From 9020a676bbe359cb158e96761ef6f1a3c32c80e5 Mon Sep 17 00:00:00 2001
From: Yannick Lecaillez <yannick.lecaillez@forgerock.com>
Date: Tue, 10 May 2016 16:42:27 +0000
Subject: [PATCH] REST2LDAP Refactoring

---
 opendj-server-legacy/src/main/java/org/opends/server/protocols/http/LDAPContext.java                             |   54 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java                                        |   60 
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java                             |  196 +
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/package-info.java                                  |    9 
 opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java                                   |    9 
 opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java             |   50 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/DirectConnectionFilter.java                  |   75 
 opendj-rest2ldap/pom.xml                                                                                         |    2 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AbstractAsynchronousConnectionDecorator.java |  130 +
 opendj-server-legacy/resource/config/http-config.json                                                            |  291 +-
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAPHttpApplication.java                      |  402 +++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java                         |   57 
 opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/InternalProxyAuthzFilter.java      |  126 +
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Utils.java                                   |   54 
 opendj-server-legacy/src/main/java/org/opends/server/protocols/http/LDAPContextInjectionFilter.java              |   81 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2Filter.java                     |  223 ++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SearchThenBindStrategy.java                  |  101 +
 opendj-server-legacy/src/main/java/org/opends/server/api/HttpEndpoint.java                                       |   11 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategy.java                  |   44 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/OptionalFilter.java                          |   71 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthenticatedConnectionContext.java                |    4 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilter.java           |  184 ++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/CachedReadConnectionDecorator.java           |  258 ++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SimpleBindStrategy.java                      |   95 +
 opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java                           |  449 ++--
 opendj-server-legacy/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java                   |  229 --
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/OptionalFilterTest.java                      |   65 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java                      |  223 +-
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilterTest.java       |  121 +
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2FilterTest.java                 |  202 ++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLDAPAttributeMapper.java                   |   29 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java                         |   39 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java                   |   17 
 /dev/null                                                                                                        |  135 -
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java                         |    9 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java                |  545 ++---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java                                     |   94 
 opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/opendj-rest2ldap-config.json                            |  358 +--
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplateTest.java                     |    5 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java                               |   44 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SASLPlainStrategy.java                       |  133 +
 opendj-server-legacy/src/main/java/org/opends/server/core/HttpEndpointConfigManager.java                         |   16 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java                                  |   27 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/package-info.java                            |   25 
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/DirectConnectionFilterTest.java              |   89 
 45 files changed, 3,667 insertions(+), 1,774 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 5e5d744..830a126 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
@@ -3,7 +3,7 @@
     // Servlet and authentication filter.
     "ldapConnectionFactories" : {
         // Unauthenticated connections used for performing bind requests.
-        "default" : {
+        "bind" : {
             // Indicates whether or not LDAP connections should be secured using
             // SSL or StartTLS. Acceptable values are:
             //
@@ -57,7 +57,7 @@
         // authentication and proxied operations (if enabled). This factory
         // will re-use the server "default" configuration.
         "root" : {
-            "inheritFrom"    : "default",
+            "inheritFrom"    : "bind",
 
             // Defines how authentication should be performed. Only "simple"
             // authentication is supported at the moment.
@@ -70,201 +70,185 @@
         }
     },
 
-    // The Rest2LDAP authentication filter configuration. The filter will be
-    // disabled if the configuration is not present. Upon successful
-    // authentication the filter will create a security context containing the
-    // following principals:
-    //
-    // "dn" - the DN of the user if known (may not be the case for sasl-plain)
-    // "id" - the username used for authentication.
-    "authenticationFilter" : {
-        // Indicates whether the filter should allow HTTP BASIC authentication.
-        "supportHTTPBasicAuthentication" : true,
+    "authorization": { 
+		// The authorization policies to use. Supported policies are "anonymous", "basic" and "oauth2".
+        "policies": [ "basic" ],
 
-        // Indicates whether the filter should allow alternative authentication
-        // and, if so, which HTTP headers it should obtain the username and
-        // password from.
-        "supportAltAuthentication"        : true,
-        "altAuthenticationUsernameHeader" : "X-OpenIDM-Username",
-        "altAuthenticationPasswordHeader" : "X-OpenIDM-Password",
+        // 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": ""
+        },
 
-        // Indicates whether the authenticated LDAP connection should be cached
-        // for use within the Rest2LDAP Servlet for subsequent LDAP operations.
-        // If this is set to true then the Servlet will not need its own LDAP
-        // connection factory and will also not need to use proxied
-        // authorization.
-        "reuseAuthenticatedConnection" : true,
+		// Use HTTP Basic authentication's information to bind to the LDAP server.
+		"basic": {
+			// Indicates whether the filter should allow alternative authentication
+			// and, if so, which HTTP headers it should obtain the username and
+			// password from.
+			"supportAltAuthentication"        : true,
+			"altAuthenticationUsernameHeader" : "X-OpenIDM-Username",
+			"altAuthenticationPasswordHeader" : "X-OpenIDM-Password",
 
-        // Specifies how LDAP authentications should be performed. The method
-        // must be one of:
-        //
-        // "simple"        - the username is an LDAP DN
-        // "sasl-plain"    - the username is an authzid which will be
-        //                   substituted into the "saslAuthzIdTemplate" using
-        //                   %s substitution
-        // "search-simple" - the user's DN will be resolved by performing an
-        //                   LDAP search using a filter constructed by
-        //                   substituting the username into the
-        //                   "searchFilterTemplate" using %s substitution.
-        "method" : "search-simple",
+			// 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,
 
-        // The connection factory which will be exclusively used for
-        // authenticating users using LDAP bind operations.
-        "bindLDAPConnectionFactory" : "default",
+			// Define which LDAP bind mechanism to use
+			// Supported mechanisms are "simple", "sasl-plain", "search"
+			"bind": "search",
 
-        // The SASL AuthzID template which will be used for "sasl-plain"
-        // authentication. The %s format parameters will be substituted with
-        // the client-provided username, using DN character escaping for DN
-        // AuthzIDs.
-        "saslAuthzIdTemplate" : "dn:uid=%s,ou=people,dc=example,dc=com",
+			// Bind to the LDAP server using the DN built from the HTTP Basic's username
+			"simple": {
+				// Connection factory used to perform the bind operation.
+				// If missing, "bind" factory will be used.
+				"ldapConnectionFactory": "bind",
 
-        // The connection factory which will be used for performing LDAP
-        // searches to locate users when "search-simple" authentication is
-        // enabled.
-        "searchLDAPConnectionFactory" : "root",
+				// The Bind 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)
+				// If missing, "%s" is used.
+				"bindDNTemplate": "uid=%s,ou=People,dc=example,dc=com"
+			},
 
-        // The search parameters to use for "search-simple" authentication. The
-        // %s filter format parameters will be substituted with the
-        // client-provided username, using LDAP filter string character escaping.
-        "searchBaseDN"         : "ou=people,dc=example,dc=com",
-        "searchScope"          : "sub", // Or "one".
-        "searchFilterTemplate" : "(&(uid=%s)(objectClass=inetOrgPerson))"
+			// Bind to the LDAP server using a SASL Plain request
+			"sasl-plain": {
+				// Connection factory used to perform the bind operation.
+				// If missing, "bind" factory will be used.
+			    "ldapConnectionFactory": "bind",
 
-        // TODO: support for HTTP sessions?
+				// Authentication identity template containing a single %s which will be replaced by the authenticating
+                // user's name. (i.e: u:%s)
+				"authcIdTemplate": "u:%s"
+			},
+			
+			// Bind to the LDAP server using the resulting DN of a search request. 
+			"search": {
+				// Connection factory used to perform the search operation.
+				// If missing, "root" factory will be used.
+				"searchLDAPConnectionFactory": "root",
+				
+				// Connection factory used to perform the bind operation.
+				// If missing, "bind" factory will be used.
+				"bindLDAPConnectionFactory": "bind",
+			
+                // The %s filter format parameters will be substituted with the client-provided username, 
+                // using LDAP filter string character escaping.
+                "baseDN"         : "ou=people,dc=example,dc=com",
+                "scope"          : "sub", // Or "one".
+                "filterTemplate" : "(&(uid=%s)(objectClass=inetOrgPerson))"
+			}
+			// TODO: support for HTTP sessions?
+		}
     },
 
-    // The Rest2LDAP Servlet configuration.
-    "servlet" : {
-        // The connection factory which will be used for performing LDAP
-        // operations. Pre-authenticated connections passed through from the
-        // authentication filter (see "reuseAuthenticatedConnection") will be
-        // used in preference to this factory. Specifically, a connection
-        // factory does not need to be configured if a connection will always
-        // be passed on from the filter, which may not always be the case
-        // if the filter is configured to use HTTP sessions.
-        "ldapConnectionFactory" : "root",
 
-        // Specifies how LDAP authorization should be performed. The method
-        // must be one of:
-        //
-        // "none"        - use connections acquired from the LDAP connection
-        //                 factory. Don't use proxied authorization, and don't
-        //                 use cached pre-authenticated connections,
-        // "reuse"       - use the connection obtained during LDAP
-        //                 authentication. If no connection was passed through
-        //                 the authorization will fail,
-        // "proxy"       - use proxied authorization with an authorization ID
-        //                 derived from the "proxyAuthzIdTemplate". Proxied
-        //                 authorization will only be used if there is no
-        //                 pre-authenticated connection available.
-        "authorizationPolicy" : "proxy",
-
-        // The AuthzID template which will be used for proxied authorization.
-        // The template should contain fields which are expected to be found in
-        // the security context create during authentication, e.g. "dn" and "id".
-        "proxyAuthzIdTemplate" : "dn:{dn}",
-
-        // The REST APIs and their LDAP attribute mappings.
-        "mappings" : {
-            "/users" : {
-                "baseDN"              : "ou=people,dc=example,dc=com",
-                "readOnUpdatePolicy"  : "controls",
-                "useSubtreeDelete"    : false,
-                "usePermissiveModify" : true,
-                "etagAttribute"       : "etag",
-                "namingStrategy"      : {
-                    "strategy"    : "clientDNNaming",
-                    "dnAttribute" : "uid"
-                },
-                "additionalLDAPAttributes" : [
-                    {
-                        "type" : "objectClass",
-                        "values" : [
-                            "top",
-                            "person",
-                            "organizationalPerson",
-                            "inetOrgPerson"
-                        ]
-                    }
-                ],
-                "attributes" : {
-                    "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
-                    "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
-                    "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
-                    "userName"    : { "simple"   : { "ldapAttribute" : "mail", "isSingleValued" : true, "writability" : "readOnly" } },
-                    "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true } },
-                    "name"        : { "object"   : {
-                        "givenName"  : { "simple" : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
-                        "familyName" : { "simple" : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } }
-                    } },
-                    "manager"     : { "reference" : {
-                        "ldapAttribute" : "manager",
-                        "baseDN"        : "ou=people,dc=example,dc=com",
-                        "primaryKey"    : "uid",
-                        "mapper"         : { "object" : {
-                            "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
-                            "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
-                        } }
-                    } },
-                    "groups"     : { "reference" : {
-                        "ldapAttribute" : "isMemberOf",
-                        "baseDN"        : "ou=groups,dc=example,dc=com",
-                        "writability"   : "readOnly",
-                        "primaryKey"    : "cn",
-                        "mapper"        : { "object" : {
-                            "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true } }
-                        } }
-                    } },
-                    "contactInformation" : { "object" : {
-                        "telephoneNumber" : { "simple" : { "ldapAttribute" : "telephoneNumber", "isSingleValued" : true } },
-                        "emailAddress"    : { "simple" : { "ldapAttribute" : "mail", "isSingleValued" : true } }
-                    } },
-                    "meta"        : { "object" : {
-                        "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
-                        "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
-                    } }
-                }
-            },
-            "/groups" : {
-                "baseDN"              : "ou=groups,dc=example,dc=com",
-                "readOnUpdatePolicy"  : "controls",
-                "useSubtreeDelete"    : false,
-                "usePermissiveModify" : true,
-                "etagAttribute"       : "etag",
-                "namingStrategy"      : {
-                    "strategy"    : "clientDNNaming",
-                    "dnAttribute" : "cn"
-                },
-                "additionalLDAPAttributes" : [
-                    {
-                        "type" : "objectClass",
-                        "values" : [
-                            "top",
-                            "groupOfUniqueNames"
-                        ]
-                    }
-                ],
-                "attributes" : {
-                    "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
-                    "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
-                    "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
-                    "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "readOnly" } },
-                    "members"    : { "reference" : {
-                        "ldapAttribute" : "uniqueMember",
-                        "baseDN"        : "dc=example,dc=com",
-                        "primaryKey"    : "uid",
-                        "mapper"        : { "object" : {
-                            "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
-                            "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
-                        } }
-                    } },
-                    "meta"        : { "object" : {
-                        "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
-                        "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
-                    } }
-                }
-            }
-        }
-    }
+	// The REST APIs and their LDAP attribute mappings.
+	"mappings" : {
+	    "/users" : {
+			"baseDN"              : "ou=people,dc=example,dc=com",
+			"readOnUpdatePolicy"  : "controls",
+			"useSubtreeDelete"    : false,
+			"usePermissiveModify" : true,
+			"etagAttribute"       : "etag",
+			"namingStrategy"      : {
+			    "strategy"    : "clientDNNaming",
+			    "dnAttribute" : "uid"
+			},
+			"additionalLDAPAttributes" : [
+			    {
+					"type" : "objectClass",
+					"values" : [
+					    "top",
+					    "person",
+					    "organizationalPerson",
+				    	"inetOrgPerson"
+					]
+			    }
+			],
+			"attributes" : {
+			    "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
+			    "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
+			    "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
+			    "userName"    : { "simple"   : { "ldapAttribute" : "mail", "isSingleValued" : true, "writability" : "readOnly" } },
+			    "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true } },
+			    "name"        : { "object"   : {
+					"givenName"  : { "simple" : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
+					"familyName" : { "simple" : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } }
+			    } },
+			    "manager"     : { "reference" : {
+					"ldapAttribute" : "manager",
+					"baseDN"        : "ou=people,dc=example,dc=com",
+					"primaryKey"    : "uid",
+					"mapper"         : { "object" : {
+					    "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
+					    "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
+					} }
+			    } },
+			    "groups"     : { "reference" : {
+					"ldapAttribute" : "isMemberOf",
+					"baseDN"        : "ou=groups,dc=example,dc=com",
+					"writability"   : "readOnly",
+					"primaryKey"    : "cn",
+					"mapper"        : { "object" : {
+					    "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true } }
+					} }
+			    } },
+			    "contactInformation" : { "object" : {
+					"telephoneNumber" : { "simple" : { "ldapAttribute" : "telephoneNumber", "isSingleValued" : true } },
+					"emailAddress"    : { "simple" : { "ldapAttribute" : "mail", "isSingleValued" : true } }
+			    } },
+			    "meta"        : { "object" : {
+					"created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
+					"lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
+			    } }
+			}
+	    },
+	    "/groups" : {
+			"baseDN"              : "ou=groups,dc=example,dc=com",
+			"readOnUpdatePolicy"  : "controls",
+			"useSubtreeDelete"    : false,
+			"usePermissiveModify" : true,
+			"etagAttribute"       : "etag",
+			"namingStrategy"      : {
+				"strategy"    : "clientDNNaming",
+				"dnAttribute" : "cn"
+			},
+			"additionalLDAPAttributes" : [
+				{
+					"type" : "objectClass",
+					"values" : [
+						"top",
+						"groupOfUniqueNames"
+					]
+				}
+			],
+			"attributes" : {
+				"schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
+				"_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
+				"_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
+				"displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "readOnly" } },
+				"members"    : { "reference" : {
+					"ldapAttribute" : "uniqueMember",
+					"baseDN"        : "dc=example,dc=com",
+					"primaryKey"    : "uid",
+					"mapper"        : { "object" : {
+						"_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
+						"displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
+					} }
+				} },
+				"meta"        : { "object" : {
+					"created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
+					"lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
+				} }
+			}
+	    }
+	}
 }
 
diff --git a/opendj-rest2ldap/pom.xml b/opendj-rest2ldap/pom.xml
index 8600474..842b3f2 100644
--- a/opendj-rest2ldap/pom.xml
+++ b/opendj-rest2ldap/pom.xml
@@ -22,7 +22,7 @@
         <groupId>org.forgerock.opendj</groupId>
         <version>4.0.0-SNAPSHOT</version>
     </parent>
-
+	
     <artifactId>opendj-rest2ldap</artifactId>
     <name>OpenDJ Commons REST Adapter</name>
     <description>This module includes APIs for accessing LDAP repositories using commons REST.</description>
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLDAPAttributeMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLDAPAttributeMapper.java
index dcf269f..a59546a 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLDAPAttributeMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AbstractLDAPAttributeMapper.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2013-2015 ForgeRock AS.
+ * Copyright 2013-2016 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -36,6 +36,7 @@
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.AttributeDescription;
+import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.LinkedAttribute;
 import org.forgerock.opendj.ldap.Modification;
@@ -100,8 +101,8 @@
 
     @Override
     Promise<List<Attribute>, ResourceException> create(
-            final RequestState requestState, final JsonPointer path, final JsonValue v) {
-        return getNewLDAPAttributes(requestState, path, v).then(
+            final Connection connection, final JsonPointer path, final JsonValue v) {
+        return getNewLDAPAttributes(connection, path, v).then(
             new Function<Attribute, List<Attribute>, ResourceException>() {
                 @Override
                 public List<Attribute> apply(Attribute newLDAPAttribute) throws ResourceException {
@@ -125,19 +126,19 @@
     }
 
     @Override
-    void getLDAPAttributes(final RequestState requestState, final JsonPointer path,
+    void getLDAPAttributes(final Connection connection, final JsonPointer path,
             final JsonPointer subPath, final Set<String> ldapAttributes) {
         ldapAttributes.add(ldapAttributeName.toString());
     }
 
-    abstract Promise<Attribute, ResourceException> getNewLDAPAttributes(
-                RequestState requestState, JsonPointer path, List<Object> newValues);
+    abstract Promise<Attribute, ResourceException> getNewLDAPAttributes(Connection connection, JsonPointer path,
+            List<Object> newValues);
 
     abstract T getThis();
 
     @Override
     Promise<List<Modification>, ResourceException> patch(
-                final RequestState requestState, final JsonPointer path, final PatchOperation operation) {
+                final Connection connection, final JsonPointer path, final PatchOperation operation) {
         try {
             final JsonPointer field = operation.getField();
             final JsonValue v = operation.getValue();
@@ -262,7 +263,7 @@
                         singletonList(new Modification(modType, emptyAttribute(ldapAttributeName))));
                 }
             } else {
-                return getNewLDAPAttributes(requestState, path, newValues)
+                return getNewLDAPAttributes(connection, path, newValues)
                         .then(new Function<Attribute, List<Modification>, ResourceException>() {
                             @Override
                             public List<Modification> apply(final Attribute value) {
@@ -278,9 +279,9 @@
     }
 
     @Override
-    Promise<List<Modification>, ResourceException> update(
-            final RequestState requestState, final JsonPointer path, final Entry e, final JsonValue v) {
-        return getNewLDAPAttributes(requestState, path, v).then(
+    Promise<List<Modification>, ResourceException> update(final Connection connection, final JsonPointer path,
+            final Entry e, final JsonValue v) {
+        return getNewLDAPAttributes(connection, path, v).then(
             new Function<Attribute, List<Modification>, ResourceException>() {
                 @Override
                 public List<Modification> apply(final Attribute newLDAPAttribute) throws ResourceException {
@@ -370,8 +371,8 @@
         }
     }
 
-    private Promise<Attribute, ResourceException> getNewLDAPAttributes(
-            final RequestState requestState, final JsonPointer path, final JsonValue v) {
+    private Promise<Attribute, ResourceException> getNewLDAPAttributes(final Connection connection,
+            final JsonPointer path, final JsonValue v) {
         try {
             // Ensure that the value is of the correct type.
             checkSchema(path, v);
@@ -380,7 +381,7 @@
                 // Skip sub-class implementation if there are no values.
                 return Promises.newResultPromise(emptyAttribute(ldapAttributeName));
             } else {
-                return getNewLDAPAttributes(requestState, path, newValues);
+                return getNewLDAPAttributes(connection, path, newValues);
             }
         } catch (final Exception ex) {
             return Promises.newExceptionPromise(asResourceException(ex));
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
index d0fd363..0660502 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012-2015 ForgeRock AS.
+ * Copyright 2012-2016 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -23,6 +23,7 @@
 import org.forgerock.json.resource.PatchOperation;
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.opendj.ldap.Attribute;
+import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.Modification;
@@ -50,8 +51,8 @@
      * action in this case, perhaps by substituting default LDAP values, or by
      * returning a failed promise with an appropriate {@link ResourceException}.
      *
-     * @param requestState
-     *            The request state.
+     * @param connection
+     *            The LDAP connection to use to perform the operation.
      * @param path
      *            The pointer from the root of the JSON resource to this
      *            attribute mapper. This may be used when constructing error
@@ -62,8 +63,7 @@
      *            in the resource.
      * @return A {@link Promise} containing the result of the operation.
      */
-    abstract Promise<List<Attribute>, ResourceException> create(
-            RequestState requestState, JsonPointer path, JsonValue v);
+    abstract Promise<List<Attribute>, ResourceException> create(Connection connection, JsonPointer path, JsonValue v);
 
     /**
      * Adds the names of the LDAP attributes required by this attribute mapper
@@ -72,8 +72,8 @@
      * Implementations should only add the names of attributes found in the LDAP
      * entry directly associated with the resource.
      *
-     * @param requestState
-     *            The request state.
+     * @param connection
+     *            The LDAP connection to use to perform the operation.
      * @param path
      *            The pointer from the root of the JSON resource to this
      *            attribute mapper. This may be used when constructing error
@@ -86,8 +86,8 @@
      *            The set into which the required LDAP attribute names should be
      *            put.
      */
-    abstract void getLDAPAttributes(
-            RequestState requestState, JsonPointer path, JsonPointer subPath, Set<String> ldapAttributes);
+    abstract void getLDAPAttributes(Connection connection, JsonPointer path, JsonPointer subPath,
+            Set<String> ldapAttributes);
 
     /**
      * Transforms the provided REST comparison filter parameters to an LDAP
@@ -98,8 +98,8 @@
      * promise must be returned with an appropriate {@link ResourceException}
      * indicating the problem which occurred.
      *
-     * @param requestState
-     *            The request state.
+     * @param connection
+     *            The LDAP connection to use to perform the operation.
      * @param path
      *            The pointer from the root of the JSON resource to this
      *            attribute mapper. This may be used when constructing error
@@ -119,7 +119,7 @@
      *            {@link FilterType#PRESENT}.
      * @return A {@link Promise} containing the result of the operation.
      */
-    abstract Promise<Filter, ResourceException> getLDAPFilter(RequestState requestState, JsonPointer path,
+    abstract Promise<Filter, ResourceException> getLDAPFilter(Connection connection, JsonPointer path,
             JsonPointer subPath, FilterType type, String operator, Object valueAssertion);
 
     /**
@@ -127,8 +127,8 @@
      * a promise once the transformation has completed. This method is invoked
      * when a REST resource is modified using a patch request.
      *
-     * @param requestState
-     *            The request state.
+     * @param connection
+     *            The LDAP connection to use to perform the operation.
      * @param path
      *            The pointer from the root of the JSON resource to this
      *            attribute mapper. This may be used when constructing error
@@ -141,7 +141,7 @@
      * @return A {@link Promise} containing the result of the operation.
      */
     abstract Promise<List<Modification>, ResourceException> patch(
-            RequestState requestState, JsonPointer path, PatchOperation operation);
+            Connection connection, JsonPointer path, PatchOperation operation);
 
     /**
      * Maps one or more LDAP attributes to their JSON representation, returning
@@ -158,8 +158,8 @@
      * they contain unexpected content, then a failed promise must be returned
      * with an appropriate exception indicating the problem which occurred.
      *
-     * @param requestState
-     *            The request state.
+     * @param connection
+     *            The LDAP connection to use to perform the operation.
      * @param path
      *            The pointer from the root of the JSON resource to this
      *            attribute mapper. This may be used when constructing error
@@ -168,7 +168,7 @@
      *            The LDAP entry to be converted to JSON.
      * @return A {@link Promise} containing the result of the operation.
      */
-    abstract Promise<JsonValue, ResourceException> read(RequestState requestState, JsonPointer path, Entry e);
+    abstract Promise<JsonValue, ResourceException> read(Connection connection, JsonPointer path, Entry e);
 
     /**
      * Maps a JSON value to one or more LDAP modifications, returning a promise
@@ -181,16 +181,16 @@
      * action in this case, perhaps by substituting default LDAP values, or by
      * returning a failed promise with an appropriate {@link ResourceException}.
      *
-     * @param requestState
-     *            The request state.
+     * @param connection
+     *            The LDAP connection to use to perform the operation.
      * @param v
      *            The JSON value to be converted to LDAP attributes, which may
      *            be {@code null} indicating that the JSON value was not present
      *            in the resource.
      * @return A {@link Promise} containing the result of the operation.
      */
-    abstract Promise<List<Modification>, ResourceException> update(
-            RequestState requestState, JsonPointer path, Entry e, JsonValue v);
+    abstract Promise<List<Modification>, ResourceException> update(Connection connection, JsonPointer path, Entry e,
+            JsonValue v);
 
     // TODO: methods for obtaining schema information (e.g. name, description, type information).
     // TODO: methods for creating sort controls.
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthenticatedConnectionContext.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthenticatedConnectionContext.java
index b991193..bffbffe 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthenticatedConnectionContext.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthenticatedConnectionContext.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions copyright [year] [name of copyright owner]".
  *
- * Copyright 2013-2015 ForgeRock AS.
+ * Copyright 2013-2016 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -78,7 +78,7 @@
      * @return The cached pre-authenticated LDAP connection which should be
      *         re-used for subsequent LDAP operations.
      */
-    Connection getConnection() {
+    public Connection getConnection() {
         return connection;
     }
 }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthorizationPolicy.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthorizationPolicy.java
deleted file mode 100644
index cb7d0fb..0000000
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthorizationPolicy.java
+++ /dev/null
@@ -1,42 +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 2013 ForgeRock AS.
- */
-
-package org.forgerock.opendj.rest2ldap;
-
-/**
- * The policy which should be for performing authorization.
- */
-public enum AuthorizationPolicy {
-    /**
-     * Use connections acquired from the LDAP connection factory. Don't use
-     * proxied authorization, and don't use cached pre-authenticated
-     * connections.
-     */
-    NONE,
-
-    /**
-     * Use the connection obtained during LDAP authentication. If no connection
-     * was passed through the authorization will fail.
-     */
-    REUSE,
-
-    /**
-     * Use proxied authorization with an authorization ID derived from the
-     * proxied authorization ID template. Proxied authorization will only be
-     * used if there is no pre-authenticated connection available.
-     */
-    PROXY;
-}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
index 101e53d..8a98cb0 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
@@ -11,11 +11,10 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2013 ForgeRock AS.
+ * Copyright 2013-2016 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
-import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.DecodeOptions;
 import org.forgerock.opendj.ldap.schema.Schema;
 
@@ -23,40 +22,20 @@
  * Common configuration options.
  */
 final class Config {
-    private final AuthorizationPolicy authzPolicy;
-    private final ConnectionFactory factory;
     private final DecodeOptions options;
-    private final AuthzIdTemplate proxiedAuthzTemplate;
     private final ReadOnUpdatePolicy readOnUpdatePolicy;
-    private final Schema schema;
     private final boolean useSubtreeDelete;
     private final boolean usePermissiveModify;
 
-    Config(final ConnectionFactory factory, final ReadOnUpdatePolicy readOnUpdatePolicy,
-            final AuthorizationPolicy authzPolicy, final AuthzIdTemplate proxiedAuthzTemplate,
-            final boolean useSubtreeDelete, final boolean usePermissiveModify, final Schema schema) {
-        this.factory = factory;
+    Config(final ReadOnUpdatePolicy readOnUpdatePolicy, final boolean useSubtreeDelete,
+            final boolean usePermissiveModify, final Schema schema) {
         this.readOnUpdatePolicy = readOnUpdatePolicy;
-        this.authzPolicy = authzPolicy;
-        this.proxiedAuthzTemplate = proxiedAuthzTemplate;
         this.useSubtreeDelete = useSubtreeDelete;
         this.usePermissiveModify = usePermissiveModify;
-        this.schema = schema;
         this.options = new DecodeOptions().setSchema(schema);
     }
 
     /**
-     * Returns the LDAP SDK connection factory which should be used when
-     * performing LDAP operations.
-     *
-     * @return The LDAP SDK connection factory which should be used when
-     *         performing LDAP operations.
-     */
-    ConnectionFactory connectionFactory() {
-        return factory;
-    }
-
-    /**
      * Returns the decoding options which should be used when decoding controls
      * in responses.
      *
@@ -68,28 +47,6 @@
     }
 
     /**
-     * Returns the authorization policy which should be used for performing LDAP
-     * operations.
-     *
-     * @return The authorization policy which should be used for performing LDAP
-     *         operations.
-     */
-    AuthorizationPolicy getAuthorizationPolicy() {
-        return authzPolicy;
-    }
-
-    /**
-     * Returns the authorization ID template which should be used when proxied
-     * authorization is enabled.
-     *
-     * @return The authorization ID template which should be used when proxied
-     *         authorization is enabled.
-     */
-    AuthzIdTemplate getProxiedAuthorizationTemplate() {
-        return proxiedAuthzTemplate;
-    }
-
-    /**
      * Returns {@code true} if modify requests should include the permissive
      * modify control.
      *
@@ -121,15 +78,4 @@
     ReadOnUpdatePolicy readOnUpdatePolicy() {
         return readOnUpdatePolicy;
     }
-
-    /**
-     * Returns the schema which should be used when attribute types and
-     * controls.
-     *
-     * @return The schema which should be used when attribute types and
-     *         controls.
-     */
-    Schema schema() {
-        return schema;
-    }
 }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/HttpAuthenticationFilter.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/HttpAuthenticationFilter.java
deleted file mode 100644
index 306cf3c..0000000
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/HttpAuthenticationFilter.java
+++ /dev/null
@@ -1,385 +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 2013-2015 ForgeRock AS.
- */
-package org.forgerock.opendj.rest2ldap;
-
-import static org.forgerock.json.resource.ResourceException.newResourceException;
-import static org.forgerock.services.context.SecurityContext.AUTHZID_DN;
-import static org.forgerock.services.context.SecurityContext.AUTHZID_ID;
-import static org.forgerock.opendj.ldap.Connections.uncloseable;
-import static org.forgerock.opendj.ldap.LdapException.newLdapException;
-import static org.forgerock.opendj.ldap.requests.Requests.newPlainSASLBindRequest;
-import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
-import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
-import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
-import static org.forgerock.util.Utils.closeSilently;
-
-import java.io.Closeable;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.StringTokenizer;
-import java.util.concurrent.atomic.AtomicReference;
-
-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.json.JsonValue;
-import org.forgerock.json.JsonValueException;
-import org.forgerock.json.resource.ResourceException;
-import org.forgerock.opendj.ldap.AuthenticationException;
-import org.forgerock.opendj.ldap.AuthorizationException;
-import org.forgerock.opendj.ldap.ByteString;
-import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.ConnectionFactory;
-import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.EntryNotFoundException;
-import org.forgerock.opendj.ldap.Filter;
-import org.forgerock.opendj.ldap.LdapException;
-import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
-import org.forgerock.opendj.ldap.ResultCode;
-import org.forgerock.opendj.ldap.SearchScope;
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.Requests;
-import org.forgerock.opendj.ldap.responses.BindResult;
-import org.forgerock.opendj.ldap.responses.SearchResultEntry;
-import org.forgerock.opendj.ldap.schema.Schema;
-import org.forgerock.services.context.Context;
-import org.forgerock.services.context.SecurityContext;
-import org.forgerock.util.AsyncFunction;
-import org.forgerock.util.promise.NeverThrowsException;
-import org.forgerock.util.promise.Promise;
-import org.forgerock.util.promise.Promises;
-
-/** An LDAP based HTTP authentication filter. */
-final class HttpAuthenticationFilter implements org.forgerock.http.Filter, Closeable {
-
-    /** Indicates how authentication should be performed. */
-    private enum AuthenticationMethod {
-        SASL_PLAIN,
-        SEARCH_SIMPLE,
-        SIMPLE
-    }
-
-    private final Schema schema = Schema.getDefaultSchema();
-    private final String altAuthenticationPasswordHeader;
-    private final String altAuthenticationUsernameHeader;
-    private final AuthenticationMethod authenticationMethod;
-    private final ConnectionFactory bindLDAPConnectionFactory;
-    private final boolean reuseAuthenticatedConnection;
-    private final String saslAuthzIdTemplate;
-    private final DN searchBaseDN;
-    private final String searchFilterTemplate;
-    private final ConnectionFactory searchLDAPConnectionFactory;
-    private final SearchScope searchScope;
-    private final boolean supportAltAuthentication;
-
-    private final boolean supportHTTPBasicAuthentication;
-
-    HttpAuthenticationFilter(final JsonValue configuration) {
-        // Parse the authentication configuration.
-        final JsonValue authnConfig = configuration.get("authenticationFilter");
-        supportHTTPBasicAuthentication = authnConfig.get("supportHTTPBasicAuthentication").required().asBoolean();
-
-        // Alternative HTTP authentication.
-        supportAltAuthentication = authnConfig.get("supportAltAuthentication").required().asBoolean();
-        if (supportAltAuthentication) {
-            altAuthenticationUsernameHeader = authnConfig.get("altAuthenticationUsernameHeader").required().asString();
-            altAuthenticationPasswordHeader = authnConfig.get("altAuthenticationPasswordHeader").required().asString();
-        } else {
-            altAuthenticationUsernameHeader = null;
-            altAuthenticationPasswordHeader = null;
-        }
-
-        // Should the authenticated connection should be cached for use by subsequent LDAP operations?
-        reuseAuthenticatedConnection = authnConfig.get("reuseAuthenticatedConnection").required().asBoolean();
-
-        // Parse the authentication method and associated parameters.
-        authenticationMethod = parseAuthenticationMethod(authnConfig);
-        switch (authenticationMethod) {
-        case SASL_PLAIN:
-            saslAuthzIdTemplate = authnConfig.get("saslAuthzIdTemplate").required().asString();
-            searchBaseDN = null;
-            searchScope = null;
-            searchFilterTemplate = null;
-            searchLDAPConnectionFactory = null;
-            break;
-        case SEARCH_SIMPLE:
-            searchBaseDN = DN.valueOf(authnConfig.get("searchBaseDN").required().asString(), schema);
-            searchScope = parseSearchScope(authnConfig);
-            searchFilterTemplate = authnConfig.get("searchFilterTemplate").required().asString();
-
-            // Parse the LDAP connection factory to be used for searches.
-            final String ldapFactoryName = authnConfig.get("searchLDAPConnectionFactory").required().asString();
-            searchLDAPConnectionFactory = Rest2LDAP
-                .configureConnectionFactory(configuration.get("ldapConnectionFactories").required(), ldapFactoryName);
-
-            saslAuthzIdTemplate = null;
-            break;
-        case SIMPLE:
-        default:
-            saslAuthzIdTemplate = null;
-            searchBaseDN = null;
-            searchScope = null;
-            searchFilterTemplate = null;
-            searchLDAPConnectionFactory = null;
-            break;
-        }
-
-        // Parse the LDAP connection factory to be used for binds.
-        final String ldapFactoryName = authnConfig.get("bindLDAPConnectionFactory").required().asString();
-        bindLDAPConnectionFactory = Rest2LDAP.configureConnectionFactory(
-            configuration.get("ldapConnectionFactories").required(), ldapFactoryName);
-    }
-
-    private static AuthenticationMethod parseAuthenticationMethod(final JsonValue configuration) {
-        if (configuration.isDefined("method")) {
-            final String method = configuration.get("method").asString();
-            if ("simple".equalsIgnoreCase(method)) {
-                return AuthenticationMethod.SIMPLE;
-            } else if ("sasl-plain".equalsIgnoreCase(method)) {
-                return AuthenticationMethod.SASL_PLAIN;
-            } else if ("search-simple".equalsIgnoreCase(method)) {
-                return AuthenticationMethod.SEARCH_SIMPLE;
-            } else {
-                throw new JsonValueException(configuration,
-                    "Illegal authentication method: must be either 'simple', 'sasl-plain', or 'search-simple'");
-            }
-        } else {
-            return AuthenticationMethod.SEARCH_SIMPLE;
-        }
-    }
-
-    private static SearchScope parseSearchScope(final JsonValue configuration) {
-        if (configuration.isDefined("searchScope")) {
-            final String scope = configuration.get("searchScope").asString();
-            if ("sub".equalsIgnoreCase(scope)) {
-                return SearchScope.WHOLE_SUBTREE;
-            } else if ("one".equalsIgnoreCase(scope)) {
-                return SearchScope.SINGLE_LEVEL;
-            } else {
-                throw new JsonValueException(configuration, "Illegal search scope: must be either 'sub' or 'one'");
-            }
-        } else {
-            return SearchScope.WHOLE_SUBTREE;
-        }
-    }
-
-    @Override
-    public Promise<Response, NeverThrowsException> filter(final Context context, final Request request,
-                                                          final Handler next) {
-        // Store the authenticated connection so that it can be re-used by the handler if needed.
-        // However, make sure that it is closed on completion.
-        try {
-            final String headerUsername =
-                supportAltAuthentication ? request.getHeaders().getFirst(altAuthenticationUsernameHeader) : null;
-            final String headerPassword =
-                supportAltAuthentication ? request.getHeaders().getFirst(altAuthenticationPasswordHeader) : null;
-            final String headerAuthorization =
-                supportHTTPBasicAuthentication ? request.getHeaders().getFirst("Authorization") : null;
-
-            final String username;
-            final char[] password;
-            if (headerUsername != null) {
-                if (headerPassword == null || headerUsername.isEmpty() || headerPassword.isEmpty()) {
-                    throw newResourceException(401);
-                }
-                username = headerUsername;
-                password = headerPassword.toCharArray();
-            } else if (headerAuthorization != null) {
-                final StringTokenizer st = new StringTokenizer(headerAuthorization);
-                final String method = st.nextToken();
-                if (method == null || !"BASIC".equalsIgnoreCase(method)) {
-                    throw newResourceException(401);
-                }
-                final String b64Credentials = st.nextToken();
-                if (b64Credentials == null) {
-                    throw newResourceException(401);
-                }
-                final String credentials = ByteString.valueOfBase64(b64Credentials).toString();
-                final String[] usernameAndPassword = credentials.split(":");
-                if (usernameAndPassword.length != 2) {
-                    throw newResourceException(401);
-                }
-                username = usernameAndPassword[0];
-                password = usernameAndPassword[1].toCharArray();
-            } else {
-                throw newResourceException(401);
-            }
-
-            // If we've got here then we have a username and password.
-            switch (authenticationMethod) {
-            case SIMPLE: {
-                final Map<String, Object> authzid;
-                authzid = new LinkedHashMap<>(2);
-                authzid.put(AUTHZID_DN, username);
-                authzid.put(AUTHZID_ID, username);
-                return doBind(
-                    context, request, next, Requests.newSimpleBindRequest(username, password), username, authzid);
-            }
-            case SASL_PLAIN: {
-                final Map<String, Object> authzid;
-                final String bindId;
-                if (saslAuthzIdTemplate.startsWith("dn:")) {
-                    final String bindDN = DN.format(saslAuthzIdTemplate.substring(3), schema, username).toString();
-                    bindId = "dn:" + bindDN;
-                    authzid = new LinkedHashMap<>(2);
-                    authzid.put(AUTHZID_DN, bindDN);
-                    authzid.put(AUTHZID_ID, username);
-                } else {
-                    bindId = String.format(saslAuthzIdTemplate, username);
-                    authzid = Collections.singletonMap(AUTHZID_ID, (Object) username);
-                }
-                return doBind(context, request, next, newPlainSASLBindRequest(bindId, password), username, authzid);
-            }
-            default: // SEARCH_SIMPLE
-                final AtomicReference<Connection> savedConnection = new AtomicReference<>();
-                return searchLDAPConnectionFactory.getConnectionAsync()
-                    .thenAsync(doSearchForUser(username, savedConnection))
-                    .thenAsync(doBindAfterSearch(context, request, next, username, password, savedConnection),
-                        returnErrorAfterFailedSearch(savedConnection));
-            }
-        } catch (final Throwable t) {
-            return asErrorResponse(t);
-        }
-    }
-
-    private AsyncFunction<Connection, SearchResultEntry, LdapException> doSearchForUser(
-            final String username, final AtomicReference<Connection> savedConnection) {
-        return new AsyncFunction<Connection, SearchResultEntry, LdapException>() {
-            @Override
-            public Promise<SearchResultEntry, LdapException> apply(final Connection connection) throws LdapException {
-                savedConnection.set(connection);
-                final Filter filter = Filter.format(searchFilterTemplate, username);
-                return connection.searchSingleEntryAsync(newSearchRequest(searchBaseDN, searchScope, filter, "1.1"));
-            }
-        };
-    }
-
-    private AsyncFunction<SearchResultEntry, Response, NeverThrowsException> doBindAfterSearch(
-            final Context context, final Request request, final Handler next, final String username,
-            final char[] password, final AtomicReference<Connection> savedConnection) {
-        return new AsyncFunction<SearchResultEntry, Response, NeverThrowsException>() {
-            @Override
-            public Promise<Response, NeverThrowsException> apply(final SearchResultEntry entry) {
-                closeConnection(savedConnection);
-                final String bindDN = entry.getName().toString();
-                final Map<String, Object> authzid = new LinkedHashMap<>(2);
-                authzid.put(AUTHZID_DN, bindDN);
-                authzid.put(AUTHZID_ID, username);
-                return doBind(context, request, next, newSimpleBindRequest(bindDN, password), username, authzid);
-            }
-        };
-    }
-
-    /**
-     * Get a bind connection and then perform the bind operation, setting the cached connection and authorization
-     * credentials on completion.
-     */
-    private Promise<Response, NeverThrowsException> doBind(
-            final Context context, final Request request, final Handler next, final BindRequest bindRequest,
-            final String authcid, final Map<String, Object> authzid) {
-        final AtomicReference<Connection> savedConnection = new AtomicReference<>();
-        return bindLDAPConnectionFactory.getConnectionAsync()
-            .thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() {
-                @Override
-                public Promise<BindResult, LdapException> apply(final Connection connection) throws LdapException {
-                    savedConnection.set(connection);
-                    return connection.bindAsync(bindRequest);
-                }
-            })
-            .thenAsync(doChain(context, request, next, authcid, authzid, savedConnection),
-                       returnErrorAfterFailedBind())
-            .thenFinally(new Runnable() {
-                @Override
-                public void run() {
-                    closeConnection(savedConnection);
-                }
-            });
-    }
-
-    private AsyncFunction<BindResult, Response, NeverThrowsException> doChain(
-            final Context context, final Request request, final Handler next, final String authcid,
-            final Map<String, Object> authzid, final AtomicReference<Connection> savedConnection) {
-        return new AsyncFunction<BindResult, Response, NeverThrowsException>() {
-            @Override
-            public Promise<Response, NeverThrowsException> apply(final BindResult result) {
-                // Pass through the authentication ID and authorization principals.
-                Context forwardedContext = new SecurityContext(context, authcid, authzid);
-
-                // Cache the pre-authenticated connection and prevent downstream
-                // components from closing it since this filter will close it.
-                if (reuseAuthenticatedConnection) {
-                    forwardedContext = new AuthenticatedConnectionContext(
-                            forwardedContext, uncloseable(savedConnection.get()));
-                }
-
-                return next.handle(forwardedContext, request);
-            }
-        };
-    }
-
-    private AsyncFunction<LdapException, Response, NeverThrowsException> returnErrorAfterFailedSearch(
-            final AtomicReference<Connection> savedConnection) {
-        return new AsyncFunction<LdapException, Response, NeverThrowsException>() {
-            @Override
-            public Promise<Response, NeverThrowsException> apply(final LdapException e) {
-                if (closeConnection(savedConnection)) {
-                    // The search error should not be passed as-is back to the user.
-                    if (e instanceof EntryNotFoundException || e instanceof MultipleEntriesFoundException) {
-                        return asErrorResponse(newLdapException(ResultCode.INVALID_CREDENTIALS, e));
-                    } else if (e instanceof AuthenticationException || e instanceof AuthorizationException) {
-                        return asErrorResponse(newLdapException(ResultCode.CLIENT_SIDE_LOCAL_ERROR, e));
-                    } else {
-                        return asErrorResponse(e);
-                    }
-                } else {
-                    return asErrorResponse(e);
-                }
-            }
-        };
-    }
-
-    private AsyncFunction<LdapException, Response, NeverThrowsException> returnErrorAfterFailedBind() {
-        return new AsyncFunction<LdapException, Response, NeverThrowsException>() {
-            @Override
-            public Promise<Response, NeverThrowsException> apply(final LdapException e) {
-                return asErrorResponse(e);
-            }
-        };
-    }
-
-    private Promise<Response, NeverThrowsException> asErrorResponse(final Throwable t) {
-        final ResourceException e = asResourceException(t);
-        final Response response =
-            new Response().setStatus(Status.valueOf(e.getCode())).setEntity(e.toJsonValue().getObject());
-        return Promises.newResultPromise(response);
-    }
-
-    private boolean closeConnection(final AtomicReference<Connection> savedConnection) {
-        final Connection connection = savedConnection.get();
-        if (connection != null) {
-            connection.close();
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void close() {
-        closeSilently(searchLDAPConnectionFactory, bindLDAPConnectionFactory);
-    }
-}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java
index f916a0d..7d1f0a5 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012-2015 ForgeRock AS.
+ * Copyright 2012-2016 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -32,6 +32,7 @@
 import org.forgerock.json.resource.PatchOperation;
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.opendj.ldap.Attribute;
+import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.Modification;
@@ -54,8 +55,8 @@
     }
 
     @Override
-    Promise<List<Attribute>, ResourceException> create(
-            final RequestState requestState, final JsonPointer path, final JsonValue v) {
+    Promise<List<Attribute>, ResourceException> create(final Connection connection, final JsonPointer path,
+            final JsonValue v) {
         if (!isNullOrEmpty(v) && !v.getObject().equals(value.getObject())) {
             return Promises.<List<Attribute>, ResourceException> newExceptionPromise(new BadRequestException(i18n(
                     "The request cannot be processed because it attempts to create the read-only field '%s'", path)));
@@ -65,13 +66,13 @@
     }
 
     @Override
-    void getLDAPAttributes(final RequestState requestState, final JsonPointer path, final JsonPointer subPath,
+    void getLDAPAttributes(final Connection connection, final JsonPointer path, final JsonPointer subPath,
             final Set<String> ldapAttributes) {
         // Nothing to do.
     }
 
     @Override
-    Promise<Filter, ResourceException> getLDAPFilter(final RequestState requestState, final JsonPointer path,
+    Promise<Filter, ResourceException> getLDAPFilter(final Connection connection, final JsonPointer path,
             final JsonPointer subPath, final FilterType type, final String operator, final Object valueAssertion) {
         final Filter filter;
         final JsonValue subValue = value.get(subPath);
@@ -109,20 +110,20 @@
     }
 
     @Override
-    Promise<List<Modification>, ResourceException> patch(final RequestState requestState, final JsonPointer path,
+    Promise<List<Modification>, ResourceException> patch(final Connection connection, final JsonPointer path,
             final PatchOperation operation) {
         return Promises.<List<Modification>, ResourceException> newExceptionPromise(new BadRequestException(i18n(
                 "The request cannot be processed because it attempts to patch the read-only field '%s'", path)));
     }
 
     @Override
-    Promise<JsonValue, ResourceException> read(final RequestState requestState, final JsonPointer path, final Entry e) {
+    Promise<JsonValue, ResourceException> read(final Connection connection, final JsonPointer path, final Entry e) {
         return Promises.newResultPromise(value.copy());
     }
 
     @Override
     Promise<List<Modification>, ResourceException> update(
-            final RequestState requestState, final JsonPointer path, final Entry e, final JsonValue v) {
+            final Connection connection, final JsonPointer path, final Entry e, final JsonValue v) {
         if (!isNullOrEmpty(v) && !v.getObject().equals(value.getObject())) {
             return Promises.<List<Modification>, ResourceException> newExceptionPromise(new BadRequestException(i18n(
                     "The request cannot be processed because it attempts to modify the read-only field '%s'", path)));
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
index 382522b..c150a44 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012-2015 ForgeRock AS.
+ * Copyright 2012-2016 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -36,7 +36,6 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
 
 import org.forgerock.json.JsonPointer;
 import org.forgerock.json.JsonValue;
@@ -174,47 +173,35 @@
                     ResourceException.newResourceException(ResourceException.BAD_REQUEST, e.getLocalizedMessage(), e));
         }
 
-        final RequestState requestState = wrap(context);
-        return requestState.getConnection()
-                .thenAsync(new AsyncFunction<Connection, ActionResponse, ResourceException>() {
-                    @Override
-                    public Promise<ActionResponse, ResourceException> apply(final Connection connection)
-                            throws ResourceException {
-                        List<JsonPointer> attrs = Collections.emptyList();
-                        return connection.searchSingleEntryAsync(searchRequest(requestState, resourceId, attrs))
-                                .thenAsync(new AsyncFunction<SearchResultEntry, ActionResponse, ResourceException>() {
-                                    @Override
-                                    public Promise<ActionResponse, ResourceException> apply(
-                                              final SearchResultEntry entry) {
-                                        PasswordModifyExtendedRequest pwdModifyRequest =
-                                                Requests.newPasswordModifyExtendedRequest();
-                                        pwdModifyRequest.setUserIdentity("dn: " + entry.getName());
-                                        pwdModifyRequest.setOldPassword(asBytes(oldPassword));
-                                        pwdModifyRequest.setNewPassword(asBytes(newPassword));
-                                        return connection.extendedRequestAsync(pwdModifyRequest)
-                                            .thenAsync(new AsyncFunction<PasswordModifyExtendedResult,
-                                                    ActionResponse, ResourceException>() {
-                                                @Override
-                                                public Promise<ActionResponse, ResourceException> apply(
-                                                        PasswordModifyExtendedResult value) throws ResourceException {
-                                                    JsonValue result = new JsonValue(new LinkedHashMap<>());
-                                                    byte[] generatedPwd = value.getGeneratedPassword();
-                                                    if (generatedPwd != null) {
-                                                        result = result.put("generatedPassword",
-                                                                ByteString.valueOfBytes(generatedPwd).toString());
-                                                    }
-                                                    return Responses.newActionResponse(result).asPromise();
-                                                }
-                                            }, ldapExceptionToResourceException());
-                                    }
-                                }, ldapExceptionToResourceException());
-                    }
-
-                    private AsyncFunction<LdapException, ActionResponse, ResourceException>
-                    ldapExceptionToResourceException() {
-                        return ldapToResourceException();
-                    }
-                }).thenFinally(close(requestState));
+        final Connection connection = context.asContext(AuthenticatedConnectionContext.class).getConnection();
+        List<JsonPointer> attrs = Collections.emptyList();
+        return connection.searchSingleEntryAsync(searchRequest(connection, resourceId, attrs))
+                         .thenAsync(new AsyncFunction<SearchResultEntry, ActionResponse, ResourceException>() {
+                             @Override
+                             public Promise<ActionResponse, ResourceException> apply(
+                                       final SearchResultEntry entry) {
+                                 PasswordModifyExtendedRequest pwdModifyRequest =
+                                         Requests.newPasswordModifyExtendedRequest();
+                                 pwdModifyRequest.setUserIdentity("dn: " + entry.getName());
+                                 pwdModifyRequest.setOldPassword(asBytes(oldPassword));
+                                 pwdModifyRequest.setNewPassword(asBytes(newPassword));
+                                 return connection.extendedRequestAsync(pwdModifyRequest)
+                                     .thenAsync(new AsyncFunction<PasswordModifyExtendedResult,
+                                             ActionResponse, ResourceException>() {
+                                         @Override
+                                         public Promise<ActionResponse, ResourceException> apply(
+                                                 PasswordModifyExtendedResult value) throws ResourceException {
+                                             JsonValue result = new JsonValue(new LinkedHashMap<>());
+                                             byte[] generatedPwd = value.getGeneratedPassword();
+                                             if (generatedPwd != null) {
+                                                 result = result.put("generatedPassword",
+                                                         ByteString.valueOfBytes(generatedPwd).toString());
+                                             }
+                                             return Responses.newActionResponse(result).asPromise();
+                                         }
+                                     }, Exceptions.<ActionResponse>toResourceException());
+                             }
+                         }, Exceptions.<ActionResponse>toResourceException());
     }
 
     private byte[] asBytes(final String s) {
@@ -222,93 +209,80 @@
     }
 
     @Override
-    public Promise<ResourceResponse, ResourceException> createInstance(
-            final Context context, final CreateRequest request) {
-        final RequestState requestState = wrap(context);
-
-        return requestState.getConnection().thenAsync(
-            new AsyncFunction<Connection, ResourceResponse, ResourceException>() {
-                @Override
-                public Promise<ResourceResponse, ResourceException> apply(final Connection connection)
-                        throws ResourceException {
-                    // Calculate entry content.
-                    return attributeMapper.create(requestState, new JsonPointer(), request.getContent())
-                            .thenAsync(new AsyncFunction<List<Attribute>, ResourceResponse, ResourceException>() {
-                                @Override
-                                public Promise<ResourceResponse, ResourceException> apply(
-                                        final List<Attribute> attributes) {
-                                    // Perform add operation.
-                                    final AddRequest addRequest = newAddRequest(DN.rootDN());
-                                    for (final Attribute attribute : additionalLDAPAttributes) {
-                                        addRequest.addAttribute(attribute);
-                                    }
-                                    for (final Attribute attribute : attributes) {
-                                        addRequest.addAttribute(attribute);
-                                    }
-                                    try {
-                                        nameStrategy.setResourceId(requestState, getBaseDN(),
-                                                request.getNewResourceId(), addRequest);
-                                    } catch (final ResourceException e) {
-                                        return Promises.newExceptionPromise(e);
-                                    }
-                                    if (config.readOnUpdatePolicy() == CONTROLS) {
-                                        addRequest.addControl(PostReadRequestControl.newControl(
-                                                false, getLDAPAttributes(requestState, request.getFields())));
-                                    }
-                                    return connection.applyChangeAsync(addRequest)
-                                                     .thenAsync(postUpdateResultAsyncFunction(requestState),
-                                                                ldapExceptionToResourceException());
-                                }
-                            });
-                }
-            }).thenFinally(close(requestState));
+    public Promise<ResourceResponse, ResourceException> createInstance(final Context context,
+            final CreateRequest request) {
+        final Connection connection = context.asContext(AuthenticatedConnectionContext.class).getConnection();
+        // Calculate entry content.
+        return attributeMapper
+                .create(connection, new JsonPointer(), request.getContent())
+                .thenAsync(new AsyncFunction<List<Attribute>, ResourceResponse, ResourceException>() {
+                    @Override
+                    public Promise<ResourceResponse, ResourceException> apply(final List<Attribute> attributes) {
+                        // Perform add operation.
+                        final AddRequest addRequest = newAddRequest(DN.rootDN());
+                        for (final Attribute attribute : additionalLDAPAttributes) {
+                            addRequest.addAttribute(attribute);
+                        }
+                        for (final Attribute attribute : attributes) {
+                            addRequest.addAttribute(attribute);
+                        }
+                        try {
+                            nameStrategy.setResourceId(connection, getBaseDN(),
+                                    request.getNewResourceId(),
+                                    addRequest);
+                        } catch (final ResourceException e) {
+                            return Promises.newExceptionPromise(e);
+                        }
+                        if (config.readOnUpdatePolicy() == CONTROLS) {
+                            addRequest.addControl(PostReadRequestControl.newControl(false,
+                                    getLDAPAttributes(connection, request.getFields())));
+                        }
+                        return connection.applyChangeAsync(addRequest)
+                                         .thenAsync(
+                                                 postUpdateResultAsyncFunction(connection),
+                                                 Exceptions.<ResourceResponse>toResourceException());
+                    }
+                });
     }
 
     @Override
     public Promise<ResourceResponse, ResourceException> deleteInstance(
             final Context context, final String resourceId, final DeleteRequest request) {
-        final RequestState requestState = wrap(context);
-        final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
-        return requestState.getConnection()
-                .thenOnResult(saveConnection(connectionHolder))
-                .thenAsync(doUpdateFunction(requestState, resourceId, request.getRevision()))
+        final Connection connection = context.asContext(AuthenticatedConnectionContext.class).getConnection();
+        return doUpdateFunction(connection, resourceId, request.getRevision())
                 .thenAsync(new AsyncFunction<DN, ResourceResponse, ResourceException>() {
                     @Override
                     public Promise<ResourceResponse, ResourceException> apply(DN dn) throws ResourceException {
                         try {
                             final ChangeRecord deleteRequest = newDeleteRequest(dn);
                             if (config.readOnUpdatePolicy() == CONTROLS) {
-                                final String[] attributes = getLDAPAttributes(requestState, request.getFields());
+                                final String[] attributes = getLDAPAttributes(connection, request.getFields());
                                 deleteRequest.addControl(PreReadRequestControl.newControl(false, attributes));
                             }
                             if (config.useSubtreeDelete()) {
                                 deleteRequest.addControl(SubtreeDeleteRequestControl.newControl(true));
                             }
                             addAssertionControl(deleteRequest, request.getRevision());
-                            return connectionHolder.get().applyChangeAsync(deleteRequest)
-                                                         .thenAsync(postUpdateResultAsyncFunction(requestState),
-                                                                    ldapExceptionToResourceException());
+                            return connection.applyChangeAsync(deleteRequest)
+                                             .thenAsync(
+                                                     postUpdateResultAsyncFunction(connection),
+                                                     Exceptions.<ResourceResponse>toResourceException());
 
                         } catch (final Exception e) {
                             return Promises.newExceptionPromise(asResourceException(e));
                         }
                     }
-                }).thenFinally(close(requestState));
+                });
     }
 
     @Override
     public Promise<ResourceResponse, ResourceException> patchInstance(
             final Context context, final String resourceId, final PatchRequest request) {
-        final RequestState requestState = wrap(context);
-
+        final Connection connection = context.asContext(AuthenticatedConnectionContext.class).getConnection();
         if (request.getPatchOperations().isEmpty()) {
-            return emptyPatchInstance(requestState, resourceId, request);
+            return emptyPatchInstance(connection, resourceId, request);
         }
-
-        final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
-        return requestState.getConnection()
-                .thenOnResult(saveConnection(connectionHolder))
-                .thenAsync(doUpdateFunction(requestState, resourceId, request.getRevision()))
+        return doUpdateFunction(connection, resourceId, request.getRevision())
                 .thenAsync(new AsyncFunction<DN, ResourceResponse, ResourceException>() {
                     @Override
                     public Promise<ResourceResponse, ResourceException> apply(final DN dn) throws ResourceException {
@@ -316,7 +290,7 @@
                         List<Promise<List<Modification>, ResourceException>> promises =
                                 new ArrayList<>(request.getPatchOperations().size());
                         for (final PatchOperation operation : request.getPatchOperations()) {
-                            promises.add(attributeMapper.patch(requestState, new JsonPointer(), operation));
+                            promises.add(attributeMapper.patch(connection, new JsonPointer(), operation));
                         }
 
                         return Promises.when(promises).thenAsync(
@@ -336,13 +310,14 @@
                                             }
 
                                             final List<String> attributes =
-                                                    asList(getLDAPAttributes(requestState, request.getFields()));
+                                                    asList(getLDAPAttributes(connection, request.getFields()));
                                             if (modifyRequest.getModifications().isEmpty()) {
                                                 // This patch is a no-op so just read the entry and check its version.
-                                                return connectionHolder.get()
-                                                        .readEntryAsync(dn, attributes)
-                                                        .thenAsync(postEmptyPatchAsyncFunction(requestState, request),
-                                                                   ldapExceptionToResourceException());
+                                                return
+                                                   connection
+                                                     .readEntryAsync(dn, attributes)
+                                                     .thenAsync(postEmptyPatchAsyncFunction(connection, request),
+                                                                Exceptions.<ResourceResponse>toResourceException());
                                             } else {
                                                 // Add controls and perform the modify request.
                                                 if (config.readOnUpdatePolicy() == CONTROLS) {
@@ -354,10 +329,11 @@
                                                             PermissiveModifyRequestControl.newControl(true));
                                                 }
                                                 addAssertionControl(modifyRequest, request.getRevision());
-                                                return connectionHolder.get()
+                                                return connection
                                                         .applyChangeAsync(modifyRequest)
-                                                        .thenAsync(postUpdateResultAsyncFunction(requestState),
-                                                                   ldapExceptionToResourceException());
+                                                        .thenAsync(
+                                                                postUpdateResultAsyncFunction(connection),
+                                                                Exceptions.<ResourceResponse>toResourceException());
                                             }
                                         } catch (final Exception e) {
                                             return Promises.newExceptionPromise(asResourceException(e));
@@ -365,27 +341,21 @@
                                     }
                                 });
                     }
-                }).thenFinally(close(requestState));
-    }
-
-    /** Just read the entry and check its version. */
-    private Promise<ResourceResponse, ResourceException> emptyPatchInstance(
-            final RequestState requestState, final String resourceId, final PatchRequest request) {
-        return requestState.getConnection()
-                .thenAsync(new AsyncFunction<Connection, ResourceResponse, ResourceException>() {
-                    @Override
-                    public Promise<ResourceResponse, ResourceException> apply(final Connection connection)
-                            throws ResourceException {
-                        SearchRequest searchRequest = searchRequest(requestState, resourceId, request.getFields());
-                        return connection.searchSingleEntryAsync(searchRequest)
-                                         .thenAsync(postEmptyPatchAsyncFunction(requestState, request),
-                                                    ldapExceptionToResourceException());
-                    }
                 });
     }
 
+    /** Just read the entry and check its version. */
+    private Promise<ResourceResponse, ResourceException> emptyPatchInstance(final Connection connection,
+            final String resourceId, final PatchRequest request) {
+        final SearchRequest searchRequest = searchRequest(connection, resourceId, request.getFields());
+        return connection
+                .searchSingleEntryAsync(searchRequest)
+                .thenAsync(postEmptyPatchAsyncFunction(connection, request),
+                           Exceptions.<ResourceResponse>toResourceException());
+    }
+
     private AsyncFunction<SearchResultEntry, ResourceResponse, ResourceException> postEmptyPatchAsyncFunction(
-            final RequestState requestState, final PatchRequest request) {
+            final Connection connection, final PatchRequest request) {
         return new AsyncFunction<SearchResultEntry, ResourceResponse, ResourceException>() {
             @Override
             public Promise<ResourceResponse, ResourceException> apply(SearchResultEntry entry)
@@ -393,7 +363,7 @@
                 try {
                     // Fail if there is a version mismatch.
                     ensureMVCCVersionMatches(entry, request.getRevision());
-                    return adaptEntry(requestState, entry);
+                    return adaptEntry(connection, entry);
                 } catch (final Exception e) {
                     return Promises.newExceptionPromise(asResourceException(e));
                 }
@@ -404,23 +374,14 @@
     @Override
     public Promise<QueryResponse, ResourceException> queryCollection(
             final Context context, final QueryRequest request, final QueryResourceHandler resourceHandler) {
-        final RequestState requestState = wrap(context);
-
-        return requestState.getConnection()
-                .thenAsync(new AsyncFunction<Connection, QueryResponse, ResourceException>() {
-                    @Override
-                    public Promise<QueryResponse, ResourceException> apply(final Connection connection)
-                            throws ResourceException {
-                        // Calculate the filter (this may require the connection).
-                        return getLDAPFilter(requestState, request.getQueryFilter())
-                                            .thenAsync(runQuery(request, resourceHandler, requestState, connection));
-                    }
-                })
-                .thenFinally(close(requestState));
+        final Connection connection = context.asContext(AuthenticatedConnectionContext.class).getConnection();
+        // Calculate the filter (this may require the connection).
+        return getLDAPFilter(connection, request.getQueryFilter())
+                .thenAsync(runQuery(request, resourceHandler, connection));
     }
 
-    private Promise<Filter, ResourceException> getLDAPFilter(
-            final RequestState requestState, final QueryFilter<JsonPointer> queryFilter) {
+    private Promise<Filter, ResourceException> getLDAPFilter(final Connection connection,
+            final QueryFilter<JsonPointer> queryFilter) {
         final QueryFilterVisitor<Promise<Filter, ResourceException>, Void, JsonPointer> visitor =
                 new QueryFilterVisitor<Promise<Filter, ResourceException>, Void, JsonPointer>() {
 
@@ -467,34 +428,34 @@
                     public Promise<Filter, ResourceException> visitContainsFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return attributeMapper.getLDAPFilter(
-                                requestState, new JsonPointer(), field, FilterType.CONTAINS, null, valueAssertion);
+                                connection, new JsonPointer(), field, FilterType.CONTAINS, null, valueAssertion);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitEqualsFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return attributeMapper.getLDAPFilter(
-                                requestState, new JsonPointer(), field, FilterType.EQUAL_TO, null, valueAssertion);
+                                connection, new JsonPointer(), field, FilterType.EQUAL_TO, null, valueAssertion);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitExtendedMatchFilter(final Void unused,
                             final JsonPointer field, final String operator, final Object valueAssertion) {
                         return attributeMapper.getLDAPFilter(
-                                requestState, new JsonPointer(), field, FilterType.EXTENDED, operator, valueAssertion);
+                                connection, new JsonPointer(), field, FilterType.EXTENDED, operator, valueAssertion);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitGreaterThanFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return attributeMapper.getLDAPFilter(
-                                requestState, new JsonPointer(), field, FilterType.GREATER_THAN, null, valueAssertion);
+                                connection, new JsonPointer(), field, FilterType.GREATER_THAN, null, valueAssertion);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitGreaterThanOrEqualToFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
-                        return attributeMapper.getLDAPFilter(requestState, new JsonPointer(), field,
+                        return attributeMapper.getLDAPFilter(connection, new JsonPointer(), field,
                                 FilterType.GREATER_THAN_OR_EQUAL_TO, null, valueAssertion);
                     }
 
@@ -502,13 +463,13 @@
                     public Promise<Filter, ResourceException> visitLessThanFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return attributeMapper.getLDAPFilter(
-                                requestState, new JsonPointer(), field, FilterType.LESS_THAN, null, valueAssertion);
+                                connection, new JsonPointer(), field, FilterType.LESS_THAN, null, valueAssertion);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitLessThanOrEqualToFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
-                        return attributeMapper.getLDAPFilter(requestState, new JsonPointer(), field,
+                        return attributeMapper.getLDAPFilter(connection, new JsonPointer(), field,
                                 FilterType.LESS_THAN_OR_EQUAL_TO, null, valueAssertion);
                     }
 
@@ -566,14 +527,14 @@
                     public Promise<Filter, ResourceException> visitPresentFilter(
                             final Void unused, final JsonPointer field) {
                         return attributeMapper.getLDAPFilter(
-                                requestState, new JsonPointer(), field, FilterType.PRESENT, null, null);
+                                connection, new JsonPointer(), field, FilterType.PRESENT, null, null);
                     }
 
                     @Override
                     public Promise<Filter, ResourceException> visitStartsWithFilter(
                             final Void unused, final JsonPointer field, final Object valueAssertion) {
                         return attributeMapper.getLDAPFilter(
-                                requestState, new JsonPointer(), field, FilterType.STARTS_WITH, null, valueAssertion);
+                                connection, new JsonPointer(), field, FilterType.STARTS_WITH, null, valueAssertion);
                     }
 
                 };
@@ -582,7 +543,7 @@
     }
 
     private AsyncFunction<Filter, QueryResponse, ResourceException> runQuery(final QueryRequest request,
-            final QueryResourceHandler resourceHandler, final RequestState requestState, final Connection connection) {
+            final QueryResourceHandler resourceHandler, final Connection connection) {
         return new AsyncFunction<Filter, QueryResponse, ResourceException>() {
             /**
              * The following fields are guarded by sequenceLock. In addition,
@@ -604,7 +565,7 @@
                 }
                 final PromiseImpl<QueryResponse, ResourceException> promise = PromiseImpl.create();
                 // Perform the search.
-                final String[] attributes = getLDAPAttributes(requestState, request.getFields());
+                final String[] attributes = getLDAPAttributes(connection, request.getFields());
                 final Filter searchFilter = ldapFilter == Filter.alwaysTrue() ? Filter.objectClassPresent()
                                                                               : ldapFilter;
                 final SearchRequest searchRequest = newSearchRequest(
@@ -660,9 +621,9 @@
                          * The best solution is probably to process the primary search results in batches using
                          * the paged results control.
                          */
-                        final String id = nameStrategy.getResourceId(requestState, entry);
+                        final String id = nameStrategy.getResourceId(connection, entry);
                         final String revision = getRevisionFromEntry(entry);
-                        attributeMapper.read(requestState, new JsonPointer(), entry)
+                        attributeMapper.read(connection, new JsonPointer(), entry)
                                        .thenOnResult(new ResultHandler<JsonValue>() {
                                            @Override
                                            public void handleResult(final JsonValue result) {
@@ -754,101 +715,84 @@
     @Override
     public Promise<ResourceResponse, ResourceException> readInstance(
             final Context context, final String resourceId, final ReadRequest request) {
-        final RequestState requestState = wrap(context);
-
-        return requestState.getConnection()
-                .thenAsync(new AsyncFunction<Connection, ResourceResponse, ResourceException>() {
+        final Connection connection = context.asContext(AuthenticatedConnectionContext.class).getConnection();
+        // Do the search.
+        SearchRequest searchRequest = searchRequest(connection, resourceId, request.getFields());
+        return connection
+                .searchSingleEntryAsync(searchRequest)
+                .thenAsync(new AsyncFunction<SearchResultEntry, ResourceResponse, ResourceException>() {
                     @Override
-                    public Promise<ResourceResponse, ResourceException> apply(Connection connection)
+                    public Promise<ResourceResponse, ResourceException> apply(SearchResultEntry entry)
                             throws ResourceException {
-                        // Do the search.
-                        SearchRequest searchRequest = searchRequest(requestState, resourceId, request.getFields());
-                        return connection.searchSingleEntryAsync(searchRequest)
-                                    .thenAsync(
-                                        new AsyncFunction<SearchResultEntry, ResourceResponse, ResourceException>() {
-                                            @Override
-                                            public Promise<ResourceResponse, ResourceException> apply(
-                                                    SearchResultEntry entry) throws ResourceException {
-                                                return adaptEntry(requestState, entry);
-                                            }
-                                        },
-                                        ldapExceptionToResourceException());
+                        return adaptEntry(connection, entry);
                     }
-                })
-                .thenFinally(close(requestState));
+                }, Exceptions.<ResourceResponse>toResourceException());
     }
 
     @Override
     public Promise<ResourceResponse, ResourceException> updateInstance(
             final Context context, final String resourceId, final UpdateRequest request) {
-        final RequestState requestState = wrap(context);
-        final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
-
-        return requestState.getConnection().thenOnResult(saveConnection(connectionHolder))
-                .thenAsync(new AsyncFunction<Connection, ResourceResponse, ResourceException>() {
+        final Connection connection = context.asContext(AuthenticatedConnectionContext.class).getConnection();
+        List<JsonPointer> attrs = Collections.emptyList();
+        SearchRequest searchRequest = searchRequest(connection, resourceId, attrs);
+        return connection
+                .searchSingleEntryAsync(searchRequest)
+                .thenAsync(new AsyncFunction<SearchResultEntry, ResourceResponse, ResourceException>() {
                     @Override
-                    public Promise<ResourceResponse, ResourceException> apply(final Connection connection)
-                            throws ResourceException {
-                        List<JsonPointer> attrs = Collections.emptyList();
-                        SearchRequest searchRequest = searchRequest(requestState, resourceId, attrs);
-                        return connection.searchSingleEntryAsync(searchRequest)
-                                .thenAsync(new AsyncFunction<SearchResultEntry, ResourceResponse, ResourceException>() {
-                                    @Override
-                                    public Promise<ResourceResponse, ResourceException> apply(
-                                            final SearchResultEntry entry) {
-                                        try {
-                                            // Fail-fast if there is a version mismatch.
-                                            ensureMVCCVersionMatches(entry, request.getRevision());
+                    public Promise<ResourceResponse, ResourceException> apply(
+                            final SearchResultEntry entry) {
+                        try {
+                            // Fail-fast if there is a version mismatch.
+                            ensureMVCCVersionMatches(entry, request.getRevision());
 
-                                            // Create the modify request.
-                                            final ModifyRequest modifyRequest = newModifyRequest(entry.getName());
-                                            if (config.readOnUpdatePolicy() == CONTROLS) {
-                                                final String[] attributes =
-                                                        getLDAPAttributes(requestState, request.getFields());
-                                                modifyRequest.addControl(
-                                                        PostReadRequestControl.newControl(false, attributes));
-                                            }
-                                            if (config.usePermissiveModify()) {
-                                                modifyRequest.addControl(
-                                                        PermissiveModifyRequestControl.newControl(true));
-                                            }
-                                            addAssertionControl(modifyRequest, request.getRevision());
+                            // Create the modify request.
+                            final ModifyRequest modifyRequest = newModifyRequest(entry.getName());
+                            if (config.readOnUpdatePolicy() == CONTROLS) {
+                                final String[] attributes =
+                                        getLDAPAttributes(connection, request.getFields());
+                                modifyRequest.addControl(
+                                        PostReadRequestControl.newControl(false, attributes));
+                            }
+                            if (config.usePermissiveModify()) {
+                                modifyRequest.addControl(
+                                        PermissiveModifyRequestControl.newControl(true));
+                            }
+                            addAssertionControl(modifyRequest, request.getRevision());
 
-                                            // Determine the set of changes that need to be performed.
-                                            return attributeMapper.update(
-                                                        requestState, new JsonPointer(), entry, request.getContent())
-                                                    .thenAsync(new AsyncFunction<
-                                                            List<Modification>, ResourceResponse, ResourceException>() {
-                                                        @Override
-                                                        public Promise<ResourceResponse, ResourceException> apply(
-                                                                List<Modification> modifications)
-                                                                throws ResourceException {
-                                                            if (modifications.isEmpty()) {
-                                                                // No changes to be performed so just return
-                                                                // the entry that we read.
-                                                                return adaptEntry(requestState, entry);
-                                                            }
-                                                            // Perform the modify operation.
-                                                            modifyRequest.getModifications().addAll(modifications);
-                                                            return connection.applyChangeAsync(modifyRequest).thenAsync(
-                                                                    postUpdateResultAsyncFunction(requestState),
-                                                                    ldapExceptionToResourceException());
-                                                        }
-                                                    });
-                                        } catch (final Exception e) {
-                                            return Promises.newExceptionPromise(asResourceException(e));
+                            // Determine the set of changes that need to be performed.
+                            return attributeMapper.update(
+                                    connection, new JsonPointer(), entry, request.getContent())
+                                    .thenAsync(new AsyncFunction<
+                                            List<Modification>, ResourceResponse, ResourceException>() {
+                                        @Override
+                                        public Promise<ResourceResponse, ResourceException> apply(
+                                                List<Modification> modifications)
+                                                throws ResourceException {
+                                            if (modifications.isEmpty()) {
+                                                // No changes to be performed so just return
+                                                // the entry that we read.
+                                                return adaptEntry(connection, entry);
+                                            }
+                                            // Perform the modify operation.
+                                            modifyRequest.getModifications().addAll(modifications);
+                                            return connection
+                                                    .applyChangeAsync(modifyRequest)
+                                                    .thenAsync(
+                                                            postUpdateResultAsyncFunction(connection),
+                                                            Exceptions.<ResourceResponse>toResourceException());
                                         }
-                                    }
-                                }, ldapExceptionToResourceException());
+                                    });
+                        } catch (final Exception e) {
+                            return Promises.newExceptionPromise(asResourceException(e));
+                        }
                     }
-                }).thenFinally(close(requestState));
+                }, Exceptions.<ResourceResponse>toResourceException());
     }
 
-    private Promise<ResourceResponse, ResourceException> adaptEntry(
-            final RequestState requestState, final Entry entry) {
-        final String actualResourceId = nameStrategy.getResourceId(requestState, entry);
+    private Promise<ResourceResponse, ResourceException> adaptEntry(final Connection connection, final Entry entry) {
+        final String actualResourceId = nameStrategy.getResourceId(connection, entry);
         final String revision = getRevisionFromEntry(entry);
-        return attributeMapper.read(requestState, new JsonPointer(), entry)
+        return attributeMapper.read(connection, new JsonPointer(), entry)
                               .then(new Function<JsonValue, ResourceResponse, ResourceException>() {
                                   @Override
                                   public ResourceResponse apply(final JsonValue value) {
@@ -867,43 +811,35 @@
         }
     }
 
-    private AsyncFunction<Connection, DN, ResourceException> doUpdateFunction(
-            final RequestState requestState, final String resourceId, final String revision) {
-        return new AsyncFunction<Connection, DN, ResourceException>() {
-            @Override
-            public Promise<DN, ResourceException> apply(Connection connection) {
-                final String ldapAttribute =
-                        (etagAttribute != null && revision != null) ? etagAttribute.toString() : "1.1";
-                final SearchRequest searchRequest =
-                        nameStrategy.createSearchRequest(requestState, getBaseDN(), resourceId)
-                                    .addAttribute(ldapAttribute);
-                if (searchRequest.getScope().equals(SearchScope.BASE_OBJECT)) {
-                    // There's no point in doing a search because we already know the DN.
-                    return Promises.newResultPromise(searchRequest.getName());
-                }
-                return connection.searchSingleEntryAsync(searchRequest)
-                        .thenAsync(new AsyncFunction<SearchResultEntry, DN, ResourceException>() {
-                            @Override
-                            public Promise<DN, ResourceException> apply(SearchResultEntry entry)
-                                    throws ResourceException {
-                                try {
-                                    // Fail-fast if there is a version mismatch.
-                                    ensureMVCCVersionMatches(entry, revision);
-                                    // Perform update operation.
-                                    return Promises.newResultPromise(entry.getName());
-                                } catch (final Exception e) {
-                                    return Promises.newExceptionPromise(asResourceException(e));
-                                }
-                            }
-                        }, new AsyncFunction<LdapException, DN, ResourceException>() {
-                            @Override
-                            public Promise<DN, ResourceException> apply(LdapException ldapException)
-                                    throws ResourceException {
-                                return Promises.newExceptionPromise(asResourceException(ldapException));
-                            }
-                        });
-            }
-        };
+    private Promise<DN, ResourceException> doUpdateFunction(final Connection connection, final String resourceId,
+            final String revision) {
+        final String ldapAttribute = (etagAttribute != null && revision != null) ? etagAttribute.toString() : "1.1";
+        final SearchRequest searchRequest = nameStrategy.createSearchRequest(connection, getBaseDN(), resourceId)
+                .addAttribute(ldapAttribute);
+        if (searchRequest.getScope().equals(SearchScope.BASE_OBJECT)) {
+            // There's no point in doing a search because we already know the DN.
+            return Promises.newResultPromise(searchRequest.getName());
+        }
+        return connection
+                .searchSingleEntryAsync(searchRequest)
+                .thenAsync(new AsyncFunction<SearchResultEntry, DN, ResourceException>() {
+                    @Override
+                    public Promise<DN, ResourceException> apply(SearchResultEntry entry) throws ResourceException {
+                        try {
+                            // Fail-fast if there is a version mismatch.
+                            ensureMVCCVersionMatches(entry, revision);
+                            // Perform update operation.
+                            return Promises.newResultPromise(entry.getName());
+                        } catch (final Exception e) {
+                            return Promises.newExceptionPromise(asResourceException(e));
+                        }
+                    }
+                }, new AsyncFunction<LdapException, DN, ResourceException>() {
+                    @Override
+                    public Promise<DN, ResourceException> apply(LdapException ldapException) throws ResourceException {
+                        return Promises.newExceptionPromise(asResourceException(ldapException));
+                    }
+                });
     }
 
     private void ensureMVCCSupported() throws NotSupportedException {
@@ -937,33 +873,32 @@
      * Determines the set of LDAP attributes to request in an LDAP read (search,
      * post-read), based on the provided list of JSON pointers.
      *
-     * @param requestState
+     * @param connection
      *          The request state.
      * @param requestedAttributes
      *          The list of resource attributes to be read.
      * @return The set of LDAP attributes associated with the resource
      *         attributes.
      */
-    private String[] getLDAPAttributes(
-            final RequestState requestState, final Collection<JsonPointer> requestedAttributes) {
+    private String[] getLDAPAttributes(final Connection connection, final Collection<JsonPointer> requestedAttributes) {
         // Get all the LDAP attributes required by the attribute mappers.
         final Set<String> requestedLDAPAttributes;
         if (requestedAttributes.isEmpty()) {
             // Full read.
             requestedLDAPAttributes = new LinkedHashSet<>();
-            attributeMapper.getLDAPAttributes(requestState, new JsonPointer(), new JsonPointer(),
+            attributeMapper.getLDAPAttributes(connection, new JsonPointer(), new JsonPointer(),
                     requestedLDAPAttributes);
         } else {
             // Partial read.
             requestedLDAPAttributes = new LinkedHashSet<>(requestedAttributes.size());
             for (final JsonPointer requestedAttribute : requestedAttributes) {
-                attributeMapper.getLDAPAttributes(requestState, new JsonPointer(), requestedAttribute,
+                attributeMapper.getLDAPAttributes(connection, new JsonPointer(), requestedAttribute,
                         requestedLDAPAttributes);
             }
         }
 
         // Get the LDAP attributes required by the Etag and name stategies.
-        nameStrategy.getLDAPAttributes(requestState, requestedLDAPAttributes);
+        nameStrategy.getLDAPAttributes(connection, requestedLDAPAttributes);
         if (etagAttribute != null) {
             requestedLDAPAttributes.add(etagAttribute.toString());
         }
@@ -975,7 +910,7 @@
     }
 
     private AsyncFunction<Result, ResourceResponse, ResourceException> postUpdateResultAsyncFunction(
-            final RequestState requestState) {
+            final Connection connection) {
         // The handler which will be invoked for the LDAP add result.
         return new AsyncFunction<Result, ResourceResponse, ResourceException>() {
             @Override
@@ -1001,7 +936,7 @@
                     entry = null;
                 }
                 if (entry != null) {
-                    return adaptEntry(requestState, entry);
+                    return adaptEntry(connection, entry);
                 } else {
                     return Promises.newResultPromise(
                             Responses.newResourceResponse(null, null, new JsonValue(Collections.emptyMap())));
@@ -1010,45 +945,21 @@
         };
     }
 
-    private AsyncFunction<LdapException, ResourceResponse, ResourceException> ldapExceptionToResourceException() {
-        return ldapToResourceException();
-    }
-
-    private <R> AsyncFunction<LdapException, R, ResourceException> ldapToResourceException() {
-        // The handler which will be invoked for the LDAP add result.
-        return new AsyncFunction<LdapException, R, ResourceException>() {
-            @Override
-            public Promise<R, ResourceException> apply(final LdapException ldapException) throws ResourceException {
-                return Promises.newExceptionPromise(asResourceException(ldapException));
-            }
-        };
-    }
-
     private SearchRequest searchRequest(
-            final RequestState requestState, final String resourceId, final List<JsonPointer> requestedAttributes) {
-        final String[] attributes = getLDAPAttributes(requestState, requestedAttributes);
-        return nameStrategy.createSearchRequest(requestState, getBaseDN(), resourceId).addAttribute(attributes);
+            final Connection connection, final String resourceId, final List<JsonPointer> requestedAttributes) {
+        final String[] attributes = getLDAPAttributes(connection, requestedAttributes);
+        return nameStrategy.createSearchRequest(connection, getBaseDN(), resourceId).addAttribute(attributes);
     }
 
-    private RequestState wrap(final Context context) {
-        return new RequestState(config, context);
-    }
-
-    private Runnable close(final RequestState requestState) {
-        return new Runnable() {
-            @Override
-            public void run() {
-                requestState.close();
-            }
-        };
-    }
-
-    private ResultHandler<Connection> saveConnection(final AtomicReference<Connection> connectionHolder) {
-        return new ResultHandler<Connection>() {
-            @Override
-            public void handleResult(Connection connection) {
-                connectionHolder.set(connection);
-            }
-        };
+    private static final class Exceptions {
+        private static <R> AsyncFunction<LdapException, R, ResourceException> toResourceException() {
+            // The handler which will be invoked for the LDAP add result.
+            return new AsyncFunction<LdapException, R, ResourceException>() {
+                @Override
+                public Promise<R, ResourceException> apply(final LdapException ldapException) throws ResourceException {
+                    return Promises.newExceptionPromise(asResourceException(ldapException));
+                }
+            };
+        }
     }
 }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java
index 69e764e..ad7d817 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions copyright [year] [name of copyright owner]".
  *
- * Copyright 2013-2015 ForgeRock AS.
+ * Copyright 2013-2016 ForgeRock AS.
  */
 
 package org.forgerock.opendj.rest2ldap;
@@ -19,6 +19,7 @@
 import java.util.Set;
 
 import org.forgerock.json.resource.ResourceException;
+import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.requests.SearchRequest;
@@ -40,8 +41,8 @@
      * Returns a search request which can be used to obtain the specified REST
      * resource.
      *
-     * @param requestState
-     *            The request state.
+     * @param connection
+     *            The LDAP connection to use to perform the operation.
      * @param baseDN
      *            The search base DN.
      * @param resourceId
@@ -49,40 +50,40 @@
      * @return A search request which can be used to obtain the specified REST
      *         resource.
      */
-    abstract SearchRequest createSearchRequest(RequestState requestState, DN baseDN, String resourceId);
+    abstract SearchRequest createSearchRequest(Connection connection, DN baseDN, String resourceId);
 
     /**
      * Adds the name of any LDAP attribute required by this name strategy to the
      * provided set.
      *
-     * @param requestState
-     *            The request state.
+     * @param connection
+     *            The LDAP connection to use to perform the operation.
      * @param ldapAttributes
      *            The set into which any required LDAP attribute name should be
      *            put.
      */
-    abstract void getLDAPAttributes(RequestState requestState, Set<String> ldapAttributes);
+    abstract void getLDAPAttributes(Connection connection, Set<String> ldapAttributes);
 
     /**
      * Retrieves the resource ID from the provided LDAP entry. Implementations
      * may use the entry DN as well as any attributes in order to determine the
      * resource ID.
      *
-     * @param requestState
-     *            The request state.
+     * @param connection
+     *            The LDAP connection to use to perform the operation.
      * @param entry
      *            The LDAP entry from which the resource ID should be obtained.
      * @return The resource ID.
      */
-    abstract String getResourceId(RequestState requestState, Entry entry);
+    abstract String getResourceId(Connection connection, Entry entry);
 
     /**
      * Sets the resource ID in the provided LDAP entry. Implementations are
      * responsible for setting the entry DN as well as any attributes associated
      * with the resource ID.
      *
-     * @param requestState
-     *            The request state.
+     * @param connection
+     *            The LDAP connection to use to perform the operation.
      * @param baseDN
      *            The baseDN to use when constructing the entry's DN.
      * @param resourceId
@@ -93,7 +94,7 @@
      * @throws ResourceException
      *             If the resource ID cannot be determined.
      */
-    abstract void setResourceId(RequestState requestState, DN baseDN, String resourceId, Entry entry)
+    abstract void setResourceId(Connection connection, DN baseDN, String resourceId, Entry entry)
             throws ResourceException;
 
 }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
index 03cc53a..508d424 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ObjectAttributeMapper.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012-2015 ForgeRock AS.
+ * Copyright 2012-2016 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -35,6 +35,7 @@
 import org.forgerock.json.resource.PatchOperation;
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.opendj.ldap.Attribute;
+import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.Modification;
@@ -87,8 +88,8 @@
     }
 
     @Override
-    Promise<List<Attribute>, ResourceException> create(
-            final RequestState requestState, final JsonPointer path, final JsonValue v) {
+    Promise<List<Attribute>, ResourceException> create(final Connection connection, final JsonPointer path,
+            final JsonValue v) {
         try {
             /*
              * First check that the JSON value is an object and that the fields
@@ -104,13 +105,13 @@
                 for (final Map.Entry<String, Object> me : v.asMap().entrySet()) {
                     final Mapping mapping = getMapping(me.getKey());
                     final JsonValue subValue = new JsonValue(me.getValue());
-                    promises.add(mapping.mapper.create(requestState, path.child(me.getKey()), subValue));
+                    promises.add(mapping.mapper.create(connection, path.child(me.getKey()), subValue));
                 }
             }
 
             // Invoke mappings for which there were no values provided.
             for (final Mapping mapping : missingMappings.values()) {
-                promises.add(mapping.mapper.create(requestState, path.child(mapping.name), null));
+                promises.add(mapping.mapper.create(connection, path.child(mapping.name), null));
             }
 
             return Promises.when(promises)
@@ -121,29 +122,29 @@
     }
 
     @Override
-    void getLDAPAttributes(final RequestState requestState, final JsonPointer path, final JsonPointer subPath,
+    void getLDAPAttributes(final Connection connection, final JsonPointer path, final JsonPointer subPath,
             final Set<String> ldapAttributes) {
         if (subPath.isEmpty()) {
             // Request all subordinate mappings.
             for (final Mapping mapping : mappings.values()) {
-                mapping.mapper.getLDAPAttributes(requestState, path.child(mapping.name), subPath, ldapAttributes);
+                mapping.mapper.getLDAPAttributes(connection, path.child(mapping.name), subPath, ldapAttributes);
             }
         } else {
             // Request single subordinate mapping.
             final Mapping mapping = getMapping(subPath);
             if (mapping != null) {
                 mapping.mapper.getLDAPAttributes(
-                        requestState, path.child(subPath.get(0)), subPath.relativePointer(), ldapAttributes);
+                        connection, path.child(subPath.get(0)), subPath.relativePointer(), ldapAttributes);
             }
         }
     }
 
     @Override
-    Promise<Filter, ResourceException> getLDAPFilter(final RequestState requestState, final JsonPointer path,
+    Promise<Filter, ResourceException> getLDAPFilter(final Connection connection, final JsonPointer path,
             final JsonPointer subPath, final FilterType type, final String operator, final Object valueAssertion) {
         final Mapping mapping = getMapping(subPath);
         if (mapping != null) {
-            return mapping.mapper.getLDAPFilter(requestState, path.child(subPath.get(0)),
+            return mapping.mapper.getLDAPFilter(connection, path.child(subPath.get(0)),
                     subPath.relativePointer(), type, operator, valueAssertion);
         } else {
             /*
@@ -156,8 +157,8 @@
     }
 
     @Override
-    Promise<List<Modification>, ResourceException> patch(
-            final RequestState requestState, final JsonPointer path, final PatchOperation operation) {
+    Promise<List<Modification>, ResourceException> patch(final Connection connection, final JsonPointer path,
+            final PatchOperation operation) {
         try {
             final JsonPointer field = operation.getField();
             final JsonValue v = operation.getValue();
@@ -180,7 +181,7 @@
                         final JsonValue subValue = new JsonValue(me.getValue());
                         final PatchOperation subOperation =
                                 operation(operation.getOperation(), field /* empty */, subValue);
-                        promises.add(mapping.mapper.patch(requestState, path.child(me.getKey()), subOperation));
+                        promises.add(mapping.mapper.patch(connection, path.child(me.getKey()), subOperation));
                     }
                 }
 
@@ -201,7 +202,7 @@
                 }
                 final PatchOperation subOperation =
                         operation(operation.getOperation(), field.relativePointer(), v);
-                return mapping.mapper.patch(requestState, path.child(fieldName), subOperation);
+                return mapping.mapper.patch(connection, path.child(fieldName), subOperation);
             }
         } catch (final Exception ex) {
             return Promises.newExceptionPromise(asResourceException(ex));
@@ -209,7 +210,7 @@
     }
 
     @Override
-    Promise<JsonValue, ResourceException> read(final RequestState requestState, final JsonPointer path, final Entry e) {
+    Promise<JsonValue, ResourceException> read(final Connection connection, final JsonPointer path, final Entry e) {
         /*
          * Use an accumulator which will aggregate the results from the
          * subordinate mappers into a single list. On completion, the
@@ -219,7 +220,7 @@
                 new ArrayList<>(mappings.size());
 
         for (final Mapping mapping : mappings.values()) {
-            promises.add(mapping.mapper.read(requestState, path.child(mapping.name), e)
+            promises.add(mapping.mapper.read(connection, path.child(mapping.name), e)
                     .then(new Function<JsonValue, Map.Entry<String, JsonValue>, ResourceException>() {
                         @Override
                         public Map.Entry<String, JsonValue> apply(final JsonValue value) {
@@ -255,7 +256,7 @@
 
     @Override
     Promise<List<Modification>, ResourceException> update(
-            final RequestState requestState, final JsonPointer path, final Entry e, final JsonValue v) {
+            final Connection connection, final JsonPointer path, final Entry e, final JsonValue v) {
         try {
             // First check that the JSON value is an object and that the fields
             // it contains are known by this mapper.
@@ -269,13 +270,13 @@
                 for (final Map.Entry<String, Object> me : v.asMap().entrySet()) {
                     final Mapping mapping = getMapping(me.getKey());
                     final JsonValue subValue = new JsonValue(me.getValue());
-                    promises.add(mapping.mapper.update(requestState, path.child(me.getKey()), e, subValue));
+                    promises.add(mapping.mapper.update(connection, path.child(me.getKey()), e, subValue));
                 }
             }
 
             // Invoke mappings for which there were no values provided.
             for (final Mapping mapping : missingMappings.values()) {
-                promises.add(mapping.mapper.update(requestState, path.child(mapping.name), e, null));
+                promises.add(mapping.mapper.update(connection, path.child(mapping.name), e, null));
             }
 
             return Promises.when(promises)
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
index cf0fd1c..140a94c 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012-2015 ForgeRock AS.
+ * Copyright 2012-2016 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -51,6 +51,7 @@
 import org.forgerock.opendj.ldap.responses.Result;
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
 import org.forgerock.opendj.ldap.responses.SearchResultReference;
+import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.util.AsyncFunction;
 import org.forgerock.util.Function;
 import org.forgerock.util.promise.ExceptionHandler;
@@ -70,14 +71,16 @@
     private static final int SEARCH_MAX_CANDIDATES = 1000;
 
     private final DN baseDN;
+    private final Schema schema;
     private Filter filter;
     private final AttributeMapper mapper;
     private final AttributeDescription primaryKey;
     private SearchScope scope = SearchScope.WHOLE_SUBTREE;
 
-    ReferenceAttributeMapper(final AttributeDescription ldapAttributeName, final DN baseDN,
+    ReferenceAttributeMapper(final Schema schema, final AttributeDescription ldapAttributeName, final DN baseDN,
         final AttributeDescription primaryKey, final AttributeMapper mapper) {
         super(ldapAttributeName);
+        this.schema = schema;
         this.baseDN = baseDN;
         this.primaryKey = primaryKey;
         this.mapper = mapper;
@@ -130,10 +133,9 @@
     }
 
     @Override
-    Promise<Filter, ResourceException> getLDAPFilter(final RequestState requestState, final JsonPointer path,
+    Promise<Filter, ResourceException> getLDAPFilter(final Connection connection, final JsonPointer path,
             final JsonPointer subPath, final FilterType type, final String operator, final Object valueAssertion) {
-
-        return mapper.getLDAPFilter(requestState, path, subPath, type, operator, valueAssertion)
+        return mapper.getLDAPFilter(connection, path, subPath, type, operator, valueAssertion)
                 .thenAsync(new AsyncFunction<Filter, Filter, ResourceException>() {
                     @Override
                     public Promise<Filter, ResourceException> apply(final Filter result) {
@@ -141,56 +143,47 @@
                         final SearchRequest request = createSearchRequest(result);
                         final List<Filter> subFilters = new LinkedList<>();
 
-                        return requestState.getConnection().thenAsync(
-                                new AsyncFunction<Connection, Filter, ResourceException>() {
-                                    @Override
-                                    public Promise<Filter, ResourceException> apply(final Connection connection)
-                                            throws ResourceException {
-                                        return connection.searchAsync(request, new SearchResultHandler() {
-                                            @Override
-                                            public boolean handleEntry(final SearchResultEntry entry) {
-                                                if (subFilters.size() < SEARCH_MAX_CANDIDATES) {
-                                                    subFilters.add(Filter.equality(
-                                                            ldapAttributeName.toString(), entry.getName()));
-                                                    return true;
-                                                } else {
-                                                    // No point in continuing - maximum candidates reached.
-                                                    return false;
-                                                }
-                                            }
-
-                                            @Override
-                                            public boolean handleReference(final SearchResultReference reference) {
-                                                // Ignore references.
-                                                return true;
-                                            }
-                                        }).then(new Function<Result, Filter, ResourceException>() {
-                                            @Override
-                                            public Filter apply(Result result) throws ResourceException {
-                                                if (subFilters.size() >= SEARCH_MAX_CANDIDATES) {
-                                                    throw asResourceException(
-                                                            newLdapException(ResultCode.ADMIN_LIMIT_EXCEEDED));
-                                                } else if (subFilters.size() == 1) {
-                                                    return subFilters.get(0);
-                                                } else {
-                                                    return Filter.or(subFilters);
-                                                }
-                                            }
-                                        }, new Function<LdapException, Filter, ResourceException>() {
-                                            @Override
-                                            public Filter apply(LdapException exception) throws ResourceException {
-                                                throw asResourceException(exception);
-                                            }
-                                        });
-                                    }
-                                });
+                        return connection.searchAsync(request, new SearchResultHandler() {
+                            @Override
+                            public boolean handleEntry(final SearchResultEntry entry) {
+                                if (subFilters.size() < SEARCH_MAX_CANDIDATES) {
+                                    subFilters.add(Filter.equality(ldapAttributeName.toString(), entry.getName()));
+                                    return true;
+                                } else {
+                                    // No point in continuing - maximum candidates reached.
+                                    return false;
+                                }
+                            }
+                            @Override
+                            public boolean handleReference(final SearchResultReference reference) {
+                                // Ignore references.
+                                return true;
+                            }
+                        }).then(new Function<Result, Filter, ResourceException>() {
+                            @Override
+                            public Filter apply(Result result) throws ResourceException {
+                                if (subFilters.size() >= SEARCH_MAX_CANDIDATES) {
+                                    throw asResourceException(
+                                            newLdapException(ResultCode.ADMIN_LIMIT_EXCEEDED));
+                                } else if (subFilters.size() == 1) {
+                                    return subFilters.get(0);
+                                } else {
+                                    return Filter.or(subFilters);
+                                }
+                            }
+                        }, new Function<LdapException, Filter, ResourceException>() {
+                            @Override
+                            public Filter apply(LdapException exception) throws ResourceException {
+                                throw asResourceException(exception);
+                            }
+                        });
                     }
                 });
     }
 
     @Override
-    Promise<Attribute, ResourceException> getNewLDAPAttributes(
-            final RequestState requestState, final JsonPointer path, final List<Object> newValues) {
+    Promise<Attribute, ResourceException> getNewLDAPAttributes(final Connection connection, final JsonPointer path,
+            final List<Object> newValues) {
         /*
          * For each value use the subordinate mapper to obtain the LDAP primary
          * key, the perform a search for each one to find the corresponding entries.
@@ -201,7 +194,7 @@
         final PromiseImpl<Attribute, ResourceException> promise = PromiseImpl.create();
 
         for (final Object value : newValues) {
-            mapper.create(requestState, path, new JsonValue(value)).thenOnResult(new ResultHandler<List<Attribute>>() {
+            mapper.create(connection, path, new JsonValue(value)).thenOnResult(new ResultHandler<List<Attribute>>() {
                 @Override
                 public void handleResult(List<Attribute> result) {
                     Attribute primaryKeyAttribute = null;
@@ -228,43 +221,38 @@
                     final ByteString primaryKeyValue = primaryKeyAttribute.firstValue();
                     final Filter filter = Filter.equality(primaryKey.toString(), primaryKeyValue);
                     final SearchRequest search = createSearchRequest(filter);
-                    requestState.getConnection().thenOnResult(new ResultHandler<Connection>() {
-                        @Override
-                        public void handleResult(Connection connection) {
-                            connection.searchSingleEntryAsync(search)
-                                      .thenOnResult(new ResultHandler<SearchResultEntry>() {
-                                          @Override
-                                          public void handleResult(final SearchResultEntry result) {
-                                              synchronized (newLDAPAttribute) {
-                                                  newLDAPAttribute.add(result.getName());
-                                              }
-                                              completeIfNecessary();
-                                          }
-                                      }).thenOnException(new ExceptionHandler<LdapException>() {
-                                          @Override
-                                          public void handleException(final LdapException error) {
-                                              ResourceException re;
-                                              try {
-                                                  throw error;
-                                              } catch (final EntryNotFoundException e) {
-                                                  re = new BadRequestException(i18n(
-                                                          "The request cannot be processed because the resource "
-                                                          + "'%s' referenced in field '%s' does not exist",
-                                                          primaryKeyValue.toString(), path));
-                                              } catch (final MultipleEntriesFoundException e) {
-                                                  re = new BadRequestException(i18n(
-                                                          "The request cannot be processed because the resource "
-                                                          + "'%s' referenced in field '%s' is ambiguous",
-                                                          primaryKeyValue.toString(), path));
-                                              } catch (final LdapException e) {
-                                                  re = asResourceException(e);
-                                              }
-                                              exception.compareAndSet(null, re);
-                                              completeIfNecessary();
-                                          }
-                                      });
-                        }
-                    });
+                    connection.searchSingleEntryAsync(search)
+                              .thenOnResult(new ResultHandler<SearchResultEntry>() {
+                                  @Override
+                                  public void handleResult(final SearchResultEntry result) {
+                                      synchronized (newLDAPAttribute) {
+                                          newLDAPAttribute.add(result.getName());
+                                      }
+                                      completeIfNecessary();
+                                  }
+                              }).thenOnException(new ExceptionHandler<LdapException>() {
+                                  @Override
+                                  public void handleException(final LdapException error) {
+                                      ResourceException re;
+                                      try {
+                                          throw error;
+                                      } catch (final EntryNotFoundException e) {
+                                          re = new BadRequestException(i18n(
+                                                  "The request cannot be processed because the resource "
+                                                  + "'%s' referenced in field '%s' does not exist",
+                                                  primaryKeyValue.toString(), path));
+                                      } catch (final MultipleEntriesFoundException e) {
+                                          re = new BadRequestException(i18n(
+                                                  "The request cannot be processed because the resource "
+                                                  + "'%s' referenced in field '%s' is ambiguous",
+                                                  primaryKeyValue.toString(), path));
+                                      } catch (final LdapException e) {
+                                          re = asResourceException(e);
+                                      }
+                                      exception.compareAndSet(null, re);
+                                      completeIfNecessary();
+                                  }
+                              });
                 }
 
                 private void completeIfNecessary() {
@@ -287,25 +275,25 @@
     }
 
     @Override
-    Promise<JsonValue, ResourceException> read(final RequestState c, final JsonPointer path, final Entry e) {
+    Promise<JsonValue, ResourceException> read(final Connection connection, final JsonPointer path, final Entry e) {
         final Attribute attribute = e.getAttribute(ldapAttributeName);
         if (attribute == null || attribute.isEmpty()) {
             return Promises.newResultPromise(null);
         } else if (attributeIsSingleValued()) {
             try {
-                final DN dn = attribute.parse().usingSchema(c.getConfig().schema()).asDN();
-                return readEntry(c, path, dn);
+                final DN dn = attribute.parse().usingSchema(schema).asDN();
+                return readEntry(connection, path, dn);
             } catch (final Exception ex) {
                 // The LDAP attribute could not be decoded.
                 return Promises.newExceptionPromise(asResourceException(ex));
             }
         } else {
             try {
-                final Set<DN> dns = attribute.parse().usingSchema(c.getConfig().schema()).asSetOfDN();
+                final Set<DN> dns = attribute.parse().usingSchema(schema).asSetOfDN();
 
                 final List<Promise<JsonValue, ResourceException>> promises = new ArrayList<>(dns.size());
                 for (final DN dn : dns) {
-                    promises.add(readEntry(c, path, dn));
+                    promises.add(readEntry(connection, path, dn));
                 }
 
                 return Promises.when(promises)
@@ -340,33 +328,30 @@
     }
 
     private Promise<JsonValue, ResourceException> readEntry(
-            final RequestState requestState, final JsonPointer path, final DN dn) {
+            final Connection connection, final JsonPointer path, final DN dn) {
         final Set<String> requestedLDAPAttributes = new LinkedHashSet<>();
-        mapper.getLDAPAttributes(requestState, path, new JsonPointer(), requestedLDAPAttributes);
-        return requestState.getConnection().thenAsync(new AsyncFunction<Connection, JsonValue, ResourceException>() {
-            @Override
-            public Promise<JsonValue, ResourceException> apply(Connection connection) throws ResourceException {
-                final Filter searchFilter = filter != null ? filter : Filter.alwaysTrue();
-                final String[] attributes = requestedLDAPAttributes.toArray(new String[requestedLDAPAttributes.size()]);
-                final SearchRequest request = newSearchRequest(dn, SearchScope.BASE_OBJECT, searchFilter, attributes);
+        mapper.getLDAPAttributes(connection, path, new JsonPointer(), requestedLDAPAttributes);
 
-                return connection.searchSingleEntryAsync(request)
-                        .thenAsync(new AsyncFunction<SearchResultEntry, JsonValue, ResourceException>() {
-                            @Override
-                            public Promise<JsonValue, ResourceException> apply(final SearchResultEntry result) {
-                                return mapper.read(requestState, path, result);
-                            }
-                        }, new AsyncFunction<LdapException, JsonValue, ResourceException>() {
-                            @Override
-                            public Promise<JsonValue, ResourceException> apply(final LdapException error) {
-                                if (error instanceof EntryNotFoundException) {
-                                    // Ignore missing entry since it cannot be mapped.
-                                    return Promises.newResultPromise(null);
-                                }
-                                return Promises.newExceptionPromise(asResourceException(error));
-                            }
-                        });
-            }
-        });
+        final Filter searchFilter = filter != null ? filter : Filter.alwaysTrue();
+        final String[] attributes = requestedLDAPAttributes.toArray(new String[requestedLDAPAttributes.size()]);
+        final SearchRequest request = newSearchRequest(dn, SearchScope.BASE_OBJECT, searchFilter, attributes);
+
+        return connection
+                .searchSingleEntryAsync(request)
+                .thenAsync(new AsyncFunction<SearchResultEntry, JsonValue, ResourceException>() {
+                    @Override
+                    public Promise<JsonValue, ResourceException> apply(final SearchResultEntry result) {
+                        return mapper.read(connection, path, result);
+                    }
+                }, new AsyncFunction<LdapException, JsonValue, ResourceException>() {
+                    @Override
+                    public Promise<JsonValue, ResourceException> apply(final LdapException error) {
+                        if (error instanceof EntryNotFoundException) {
+                            // Ignore missing entry since it cannot be mapped.
+                            return Promises.newResultPromise(null);
+                        }
+                        return Promises.newExceptionPromise(asResourceException(error));
+                    }
+                });
     }
 }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/RequestState.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/RequestState.java
deleted file mode 100644
index 486d6f6..0000000
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/RequestState.java
+++ /dev/null
@@ -1,435 +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 2013-2016 ForgeRock AS.
- */
-package org.forgerock.opendj.rest2ldap;
-
-import java.io.Closeable;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CountDownLatch;
-
-import org.forgerock.json.resource.InternalServerErrorException;
-import org.forgerock.json.resource.ResourceException;
-import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
-import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.ConnectionEventListener;
-import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.LdapException;
-import org.forgerock.opendj.ldap.LdapPromise;
-import org.forgerock.opendj.ldap.IntermediateResponseHandler;
-import org.forgerock.opendj.ldap.LdapResultHandler;
-import org.forgerock.opendj.ldap.SearchResultHandler;
-import org.forgerock.opendj.ldap.SearchScope;
-import org.forgerock.opendj.ldap.controls.Control;
-import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
-import org.forgerock.opendj.ldap.requests.AbandonRequest;
-import org.forgerock.opendj.ldap.requests.AddRequest;
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.CompareRequest;
-import org.forgerock.opendj.ldap.requests.DeleteRequest;
-import org.forgerock.opendj.ldap.requests.ExtendedRequest;
-import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
-import org.forgerock.opendj.ldap.requests.ModifyRequest;
-import org.forgerock.opendj.ldap.requests.Request;
-import org.forgerock.opendj.ldap.requests.SearchRequest;
-import org.forgerock.opendj.ldap.requests.UnbindRequest;
-import org.forgerock.opendj.ldap.responses.BindResult;
-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.responses.SearchResultEntry;
-import org.forgerock.opendj.ldap.responses.SearchResultReference;
-import org.forgerock.services.context.Context;
-import org.forgerock.services.context.SecurityContext;
-import org.forgerock.util.promise.ExceptionHandler;
-import org.forgerock.util.promise.Promise;
-import org.forgerock.util.promise.PromiseImpl;
-import org.forgerock.util.promise.Promises;
-import org.forgerock.util.promise.ResultHandler;
-
-import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*;
-import static org.forgerock.opendj.rest2ldap.Utils.*;
-
-/**
- * Common request information passed to containers and mappers.
- * A new @{code RequestState} is allocated for each REST request.
- */
-final class RequestState implements Closeable {
-
-    /** A cached read request - see cachedReads for more information. */
-    private static final class CachedRead implements SearchResultHandler, LdapResultHandler<Result> {
-        private SearchResultEntry cachedEntry;
-        private final String cachedFilterString;
-        /** Guarded by cachedPromiseLatch. */
-        private LdapPromise<Result> cachedPromise;
-        private final CountDownLatch cachedPromiseLatch = new CountDownLatch(1);
-        private final SearchRequest cachedRequest;
-        private volatile Result cachedResult;
-        private final ConcurrentLinkedQueue<SearchResultHandler> waitingResultHandlers = new ConcurrentLinkedQueue<>();
-
-        CachedRead(final SearchRequest request, final SearchResultHandler resultHandler) {
-            this.cachedRequest = request;
-            this.cachedFilterString = request.getFilter().toString();
-            this.waitingResultHandlers.add(resultHandler);
-        }
-
-        @Override
-        public boolean handleEntry(final SearchResultEntry entry) {
-            cachedEntry = entry;
-            return true;
-        }
-
-        @Override
-        public void handleException(final LdapException exception) {
-            handleResult(exception.getResult());
-        }
-
-        @Override
-        public boolean handleReference(final SearchResultReference reference) {
-            // Ignore - should never happen for a base object search.
-            return true;
-        }
-
-        @Override
-        public void handleResult(final Result result) {
-            cachedResult = result;
-            drainQueue();
-        }
-
-        void addResultHandler(final SearchResultHandler resultHandler) {
-            // Fast path.
-            if (cachedResult != null) {
-                invokeResultHandler(resultHandler);
-                return;
-            }
-            // Enqueue and re-check.
-            waitingResultHandlers.add(resultHandler);
-            if (cachedResult != null) {
-                drainQueue();
-            }
-        }
-
-        LdapPromise<Result> getPromise() {
-            // Perform uninterrupted wait since this method is unlikely to block for a long time.
-            boolean wasInterrupted = false;
-            while (true) {
-                try {
-                    cachedPromiseLatch.await();
-                    if (wasInterrupted) {
-                        Thread.currentThread().interrupt();
-                    }
-                    return cachedPromise;
-                } catch (final InterruptedException e) {
-                    wasInterrupted = true;
-                }
-            }
-        }
-
-        boolean isMatchingRead(final SearchRequest request) {
-            // Cached reads are always base object.
-            return request.getScope().equals(SearchScope.BASE_OBJECT)
-                    // Filters must match.
-                    && request.getFilter().toString().equals(cachedFilterString)
-                    // List of requested attributes must match.
-                    && request.getAttributes().equals(cachedRequest.getAttributes());
-        }
-
-        void setPromise(final LdapPromise<Result> promise) {
-            cachedPromise = promise;
-            cachedPromiseLatch.countDown();
-        }
-
-        private void drainQueue() {
-            SearchResultHandler resultHandler;
-            while ((resultHandler = waitingResultHandlers.poll()) != null) {
-                invokeResultHandler(resultHandler);
-            }
-        }
-
-        private void invokeResultHandler(final SearchResultHandler searchResultHandler) {
-            if (cachedEntry != null) {
-                searchResultHandler.handleEntry(cachedEntry);
-            }
-        }
-    }
-
-    /**
-     * An LRU cache of recent reads requests. This is used in order to reduce
-     * the number of repeated read operations performed when resolving DN
-     * references.
-     */
-    @SuppressWarnings("serial")
-    private final Map<DN, CachedRead> cachedReads = new LinkedHashMap<DN, CachedRead>() {
-        private static final int MAX_CACHED_ENTRIES = 32;
-
-        @Override
-        protected boolean removeEldestEntry(final Map.Entry<DN, CachedRead> eldest) {
-            return size() > MAX_CACHED_ENTRIES;
-        }
-    };
-
-    private final Config config;
-    private final Context context;
-    private Connection connection;
-    private Control proxiedAuthzControl;
-
-    RequestState(final Config config, final Context context) {
-        this.config = config;
-        this.context = context;
-
-        /* Re-use the pre-authenticated connection if available and the authorization policy allows. */
-        if (config.getAuthorizationPolicy() != AuthorizationPolicy.NONE
-                && context.containsContext(AuthenticatedConnectionContext.class)) {
-            this.connection = wrap(context.asContext(AuthenticatedConnectionContext.class).getConnection());
-        } else {
-            this.connection = null; // We'll allocate the connection.
-        }
-    }
-
-    @Override
-    public void close() {
-        connection.close();
-    }
-
-    Config getConfig() {
-        return config;
-    }
-
-    Context getContext() {
-        return context;
-    }
-
-    /**
-     * Performs common processing required before handling an HTTP request,
-     * including calculating the proxied authorization request control. Then
-     * return a promise containing a valid LDAP connection or a
-     * {@link ResourceException} if an error is detected.
-     * <p>
-     * This method should be called at most once per request.
-     * @return A {@link Promise} containing a valid {@link Connection}
-     */
-    Promise<Connection, ResourceException> getConnection() {
-        /*
-         * Compute the proxied authorization control from the content of the
-         * security context if present. Only do this if we are not using a
-         * cached connection since cached connections are supposed to have been
-         * pre-authenticated and therefore do not require proxied authorization.
-         */
-        if (connection == null && config.getAuthorizationPolicy() == AuthorizationPolicy.PROXY) {
-            if (context.containsContext(SecurityContext.class)) {
-                try {
-                    final SecurityContext securityContext = context.asContext(SecurityContext.class);
-                    final String authzId = config.getProxiedAuthorizationTemplate().formatAsAuthzId(
-                            securityContext.getAuthorization(), config.schema());
-                    proxiedAuthzControl = ProxiedAuthV2RequestControl.newControl(authzId);
-                } catch (final ResourceException e) {
-                    return Promises.newExceptionPromise(e);
-                }
-            } else {
-                return Promises.<Connection, ResourceException> newExceptionPromise(new InternalServerErrorException(
-                        i18n("The request could not be authorized because it did not contain a security context")));
-            }
-        }
-
-        /*
-         * Now get the LDAP connection to use for processing subsequent LDAP
-         * requests. A null factory indicates that Rest2LDAP has been configured
-         * to re-use the LDAP connection which was used for authentication.
-         */
-        if (connection != null) {
-            return Promises.newResultPromise(connection);
-        } else if (config.connectionFactory() != null) {
-            final PromiseImpl<Connection, ResourceException> promise = PromiseImpl.create();
-            config.connectionFactory().getConnectionAsync().thenOnResult(new ResultHandler<Connection>() {
-                @Override
-                public final void handleResult(final Connection result) {
-                    connection = wrap(result);
-                    promise.handleResult(connection);
-                }
-            }).thenOnException(new ExceptionHandler<LdapException>() {
-                @Override
-                public final void handleException(final LdapException exception) {
-                    promise.handleException(asResourceException(exception));
-                }
-            });
-            return promise;
-        } else {
-            return Promises.<Connection, ResourceException> newExceptionPromise(new InternalServerErrorException(
-                    i18n("The request could not be processed because there was no LDAP connection available for use")));
-        }
-    }
-
-    /**
-     * Adds read caching support to the provided connection as well
-     * functionality which automatically adds the proxied authorization control
-     * if needed.
-     */
-    private Connection wrap(final Connection connection) {
-        /* We only use async methods so no need to wrap sync methods. */
-        return new AbstractAsynchronousConnection() {
-            @Override
-            public LdapPromise<Void> abandonAsync(final AbandonRequest request) {
-                return connection.abandonAsync(request);
-            }
-
-            @Override
-            public LdapPromise<Result> addAsync(final AddRequest request,
-                final IntermediateResponseHandler intermediateResponseHandler) {
-                return connection.addAsync(withControls(request), intermediateResponseHandler);
-            }
-
-            @Override
-            public void addConnectionEventListener(final ConnectionEventListener listener) {
-                connection.addConnectionEventListener(listener);
-            }
-
-            @Override
-            public LdapPromise<BindResult> bindAsync(final BindRequest request,
-                final IntermediateResponseHandler intermediateResponseHandler) {
-                /*
-                 * Simple brute force implementation in case the bind operation
-                 * modifies an entry: clear the cachedReads.
-                 */
-                evictAll();
-                return connection.bindAsync(request, intermediateResponseHandler);
-            }
-
-            @Override
-            public void close() {
-                connection.close();
-            }
-
-            @Override
-            public void close(final UnbindRequest request, final String reason) {
-                connection.close(request, reason);
-            }
-
-            @Override
-            public LdapPromise<CompareResult> compareAsync(final CompareRequest request,
-                final IntermediateResponseHandler intermediateResponseHandler) {
-                return connection.compareAsync(withControls(request), intermediateResponseHandler);
-            }
-
-            @Override
-            public LdapPromise<Result> deleteAsync(final DeleteRequest request,
-                final IntermediateResponseHandler intermediateResponseHandler) {
-                evict(request.getName());
-                return connection.deleteAsync(withControls(request), intermediateResponseHandler);
-            }
-
-            @Override
-            public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(final ExtendedRequest<R> request,
-                final IntermediateResponseHandler intermediateResponseHandler) {
-                /*
-                 * Simple brute force implementation in case the extended
-                 * operation modifies an entry: clear the cachedReads.
-                 */
-                evictAll();
-                return connection.extendedRequestAsync(withControls(request), intermediateResponseHandler);
-            }
-
-            @Override
-            public boolean isClosed() {
-                return connection.isClosed();
-            }
-
-            @Override
-            public boolean isValid() {
-                return connection.isValid();
-            }
-
-            @Override
-            public LdapPromise<Result> modifyAsync(final ModifyRequest request,
-                final IntermediateResponseHandler intermediateResponseHandler) {
-                evict(request.getName());
-                return connection.modifyAsync(withControls(request), intermediateResponseHandler);
-            }
-
-            @Override
-            public LdapPromise<Result> modifyDNAsync(final ModifyDNRequest request,
-                final IntermediateResponseHandler intermediateResponseHandler) {
-                // Simple brute force implementation: clear the cachedReads.
-                evictAll();
-                return connection.modifyDNAsync(withControls(request), intermediateResponseHandler);
-            }
-
-            @Override
-            public void removeConnectionEventListener(final ConnectionEventListener listener) {
-                connection.removeConnectionEventListener(listener);
-            }
-
-            /** Try and re-use a cached result if possible. */
-            @Override
-            public LdapPromise<Result> searchAsync(final SearchRequest request,
-                final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
-                /*
-                 * Don't attempt caching if this search is not a read (base
-                 * object), or if the search request passed in an intermediate
-                 * response handler.
-                 */
-                if (!request.getScope().equals(SearchScope.BASE_OBJECT) || intermediateResponseHandler != null) {
-                    return connection.searchAsync(withControls(request), intermediateResponseHandler, entryHandler);
-                }
-
-                // This is a read request and a candidate for caching.
-                final CachedRead cachedRead;
-                synchronized (cachedReads) {
-                    cachedRead = cachedReads.get(request.getName());
-                }
-                if (cachedRead != null && cachedRead.isMatchingRead(request)) {
-                    // The cached read matches this read request.
-                    cachedRead.addResultHandler(entryHandler);
-                    return cachedRead.getPromise();
-                } else {
-                    // Cache the read, possibly evicting a non-matching cached read.
-                    final CachedRead pendingCachedRead = new CachedRead(request, entryHandler);
-                    synchronized (cachedReads) {
-                        cachedReads.put(request.getName(), pendingCachedRead);
-                    }
-                    final LdapPromise<Result> promise = connection
-                            .searchAsync(withControls(request), intermediateResponseHandler, pendingCachedRead)
-                            .thenOnResult(pendingCachedRead).thenOnException(pendingCachedRead);
-                    pendingCachedRead.setPromise(promise);
-                    return promise;
-                }
-            }
-
-            @Override
-            public String toString() {
-                return connection.toString();
-            }
-
-            private void evict(final DN name) {
-                synchronized (cachedReads) {
-                    cachedReads.remove(name);
-                }
-            }
-
-            private void evictAll() {
-                synchronized (cachedReads) {
-                    cachedReads.clear();
-                }
-            }
-
-            private <R extends Request> R withControls(final R request) {
-                if (proxiedAuthzControl != null) {
-                    request.addControl(proxiedAuthzControl);
-                }
-                return request;
-            }
-        };
-    }
-}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index 7163989..3f0a2ed 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -49,6 +49,7 @@
 import org.forgerock.opendj.ldap.AuthenticationException;
 import org.forgerock.opendj.ldap.AuthorizationException;
 import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.ConnectionException;
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.ConstraintViolationException;
@@ -92,12 +93,9 @@
     /** A builder for incrementally constructing LDAP resource collections. */
     public static final class Builder {
         private final List<Attribute> additionalLDAPAttributes = new LinkedList<>();
-        private AuthorizationPolicy authzPolicy = AuthorizationPolicy.NONE;
         private DN baseDN; // TODO: support template variables.
         private AttributeDescription etagAttribute;
-        private ConnectionFactory factory;
         private NameStrategy nameStrategy;
-        private AuthzIdTemplate proxiedAuthzTemplate;
         private ReadOnUpdatePolicy readOnUpdatePolicy = CONTROLS;
         private AttributeMapper rootMapper;
         private Schema schema = Schema.getDefaultSchema();
@@ -141,18 +139,6 @@
         }
 
         /**
-         * Sets the policy which should be for performing authorization.
-         *
-         * @param policy
-         *            The policy which should be for performing authorization.
-         * @return A reference to this LDAP resource collection builder.
-         */
-        public Builder authorizationPolicy(final AuthorizationPolicy policy) {
-            this.authzPolicy = ensureNotNull(policy);
-            return this;
-        }
-
-        /**
          * Sets the base DN beneath which LDAP entries (resources) are to be found.
          *
          * @param dn
@@ -186,30 +172,8 @@
             if (rootMapper == null) {
                 throw new IllegalStateException("No mappings provided");
             }
-            switch (authzPolicy) {
-            case NONE:
-                if (factory == null) {
-                    throw new IllegalStateException(
-                            "A connection factory must be specified when the authorization policy is 'none'");
-                }
-                break;
-            case PROXY:
-                if (proxiedAuthzTemplate == null) {
-                    throw new IllegalStateException(
-                            "Proxied authorization enabled but no template defined");
-                }
-                if (factory == null) {
-                    throw new IllegalStateException(
-                            "A connection factory must be specified when using proxied authorization");
-                }
-                break;
-            case REUSE:
-                // This is always ok.
-                break;
-            }
-            return new LDAPCollectionResourceProvider(baseDN, rootMapper, nameStrategy,
-                    etagAttribute, new Config(factory, readOnUpdatePolicy, authzPolicy,
-                            proxiedAuthzTemplate, useSubtreeDelete, usePermissiveModify, schema),
+            return new LDAPCollectionResourceProvider(baseDN, rootMapper, nameStrategy, etagAttribute,
+                    new Config(readOnUpdatePolicy, useSubtreeDelete, usePermissiveModify, schema),
                     additionalLDAPAttributes);
         }
 
@@ -283,23 +247,6 @@
         }
 
         /**
-         * Sets the LDAP connection factory to be used for accessing the LDAP
-         * directory. Each HTTP request will obtain a single connection from the
-         * factory and then close it once the HTTP response has been sent. It is
-         * recommended that the provided connection factory supports connection
-         * pooling.
-         *
-         * @param factory
-         *            The LDAP connection factory to be used for accessing the
-         *            LDAP directory.
-         * @return A reference to this LDAP resource collection builder.
-         */
-        public Builder ldapConnectionFactory(final ConnectionFactory factory) {
-            this.factory = factory;
-            return this;
-        }
-
-        /**
          * Sets the attribute mapper which should be used for mapping JSON
          * resources to and from LDAP entries.
          *
@@ -313,23 +260,6 @@
         }
 
         /**
-         * Sets the authorization ID template which will be used for proxied
-         * authorization. Template parameters are specified by including the
-         * parameter name surrounded by curly braces. The template should
-         * contain fields which are expected to be found in the security context
-         * create during authentication, e.g. "dn:{dn}" or "u:{id}".
-         *
-         * @param template
-         *            The authorization ID template which will be used for
-         *            proxied authorization.
-         * @return A reference to this LDAP resource collection builder.
-         */
-        public Builder proxyAuthzIdTemplate(final String template) {
-            this.proxiedAuthzTemplate = template != null ? new AuthzIdTemplate(template) : null;
-            return this;
-        }
-
-        /**
          * Sets the policy which should be used in order to read an entry before
          * it is deleted, or after it is added or modified. The default read on
          * update policy is to use {@link ReadOnUpdatePolicy#CONTROLS controls}.
@@ -709,23 +639,23 @@
         }
 
         @Override
-        SearchRequest createSearchRequest(final RequestState requestState, final DN baseDN, final String resourceId) {
+        SearchRequest createSearchRequest(final Connection connection, final DN baseDN, final String resourceId) {
             return newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, Filter.equality(idAttribute
                     .toString(), resourceId));
         }
 
         @Override
-        void getLDAPAttributes(final RequestState requestState, final Set<String> ldapAttributes) {
+        void getLDAPAttributes(final Connection connection, final Set<String> ldapAttributes) {
             ldapAttributes.add(idAttribute.toString());
         }
 
         @Override
-        String getResourceId(final RequestState requestState, final Entry entry) {
+        String getResourceId(final Connection connection, final Entry entry) {
             return entry.parseAttribute(idAttribute).asString();
         }
 
         @Override
-        void setResourceId(final RequestState requestState, final DN baseDN, final String resourceId,
+        void setResourceId(final Connection connection, final DN baseDN, final String resourceId,
                 final Entry entry) throws ResourceException {
             if (isServerProvided) {
                 if (resourceId != null) {
@@ -749,23 +679,23 @@
         }
 
         @Override
-        SearchRequest createSearchRequest(final RequestState requestState, final DN baseDN, final String resourceId) {
+        SearchRequest createSearchRequest(final Connection connection, final DN baseDN, final String resourceId) {
             return newSearchRequest(baseDN.child(rdn(resourceId)), SearchScope.BASE_OBJECT, Filter
                     .objectClassPresent());
         }
 
         @Override
-        void getLDAPAttributes(final RequestState requestState, final Set<String> ldapAttributes) {
+        void getLDAPAttributes(final Connection connection, final Set<String> ldapAttributes) {
             ldapAttributes.add(attribute.toString());
         }
 
         @Override
-        String getResourceId(final RequestState requestState, final Entry entry) {
+        String getResourceId(final Connection connection, final Entry entry) {
             return entry.parseAttribute(attribute).asString();
         }
 
         @Override
-        void setResourceId(final RequestState requestState, final DN baseDN, final String resourceId,
+        void setResourceId(final Connection connection, final DN baseDN, final String resourceId,
                 final Entry entry) throws ResourceException {
             if (resourceId != null) {
                 entry.setName(baseDN.child(rdn(resourceId)));
@@ -929,7 +859,7 @@
      */
     public static ReferenceAttributeMapper reference(final AttributeDescription attribute,
             final DN baseDN, final AttributeDescription primaryKey, final AttributeMapper mapper) {
-        return new ReferenceAttributeMapper(attribute, baseDN, primaryKey, mapper);
+        return new ReferenceAttributeMapper(Schema.getDefaultSchema(), attribute, baseDN, primaryKey, mapper);
     }
 
     /**
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 54a8b25..41a3b08 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
@@ -11,123 +11,134 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions copyright [year] [name of copyright owner]".
  *
- * Copyright 2015 ForgeRock AS.
+ * Copyright 2015-2016 ForgeRock AS.
  */
 
 package org.forgerock.opendj.rest2ldap;
 
-import static org.forgerock.http.util.Json.*;
+import static org.forgerock.http.util.Json.readJsonLenient;
 import static org.forgerock.opendj.rest2ldap.Rest2LDAP.configureConnectionFactory;
-import static org.forgerock.util.Utils.*;
+import static org.forgerock.util.Reject.checkNotNull;
+import static org.forgerock.util.Utils.closeSilently;
 
-import java.io.Closeable;
 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 org.forgerock.http.Filter;
 import org.forgerock.http.Handler;
 import org.forgerock.http.HttpApplication;
 import org.forgerock.http.HttpApplicationException;
+import org.forgerock.http.filter.Filters;
 import org.forgerock.http.handler.Handlers;
 import org.forgerock.http.io.Buffer;
+import org.forgerock.http.protocol.Headers;
 import org.forgerock.http.protocol.Request;
 import org.forgerock.http.protocol.Response;
 import org.forgerock.json.JsonValue;
-import org.forgerock.json.resource.CollectionResourceProvider;
 import org.forgerock.json.resource.RequestHandler;
 import org.forgerock.json.resource.Router;
 import org.forgerock.json.resource.http.CrestHttp;
+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.services.context.Context;
+import org.forgerock.services.context.SecurityContext;
 import org.forgerock.util.Factory;
-import org.forgerock.util.Reject;
+import org.forgerock.util.Function;
+import org.forgerock.util.Pair;
 import org.forgerock.util.promise.NeverThrowsException;
 import org.forgerock.util.promise.Promise;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /** Rest2ldap HTTP application. */
-public final class Rest2LDAPHttpApplication implements HttpApplication {
+public class Rest2LDAPHttpApplication implements HttpApplication {
+    private static final String DEFAULT_ROOT_FACTORY = "root";
+    private static final String DEFAULT_BIND_FACTORY = "bind";
+
     private static final Logger LOG = LoggerFactory.getLogger(Rest2LDAPHttpApplication.class);
 
-    private static final class HttpHandler implements Handler, Closeable {
-        private final ConnectionFactory ldapConnectionFactory;
-        private final Handler delegate;
+    /** URL to the JSON configuration file. */
+    protected final URL configurationUrl;
 
-        HttpHandler(final JsonValue configuration) {
-            ldapConnectionFactory = createLdapConnectionFactory(configuration);
-            try {
-                delegate = CrestHttp.newHttpHandler(createRouter(configuration, ldapConnectionFactory));
-            } catch (final RuntimeException e) {
-                closeSilently(ldapConnectionFactory);
-                throw e;
-            }
-        }
+    /** Schema used to perform DN validations. */
+    protected final Schema schema;
 
-        private static RequestHandler createRouter(
-                final JsonValue configuration, final ConnectionFactory ldapConnectionFactory) {
-            final AuthorizationPolicy authzPolicy = configuration.get("servlet")
-                    .get("authorizationPolicy")
-                    .required()
-                    .asEnum(AuthorizationPolicy.class);
-            final String proxyAuthzTemplate = configuration.get("servlet").get("proxyAuthzIdTemplate").asString();
-            final JsonValue mappings = configuration.get("servlet").get("mappings").required();
+    private final Map<String, ConnectionFactory> connectionFactories = new HashMap<>();
 
-            final Router router = new Router();
-            for (final String mappingUrl : mappings.keys()) {
-                final JsonValue mapping = mappings.get(mappingUrl);
-                final CollectionResourceProvider provider = Rest2LDAP.builder()
-                        .ldapConnectionFactory(ldapConnectionFactory)
-                        .authorizationPolicy(authzPolicy)
-                        .proxyAuthzIdTemplate(proxyAuthzTemplate)
-                        .configureMapping(mapping)
-                        .build();
-                router.addRoute(Router.uriTemplate(mappingUrl), provider);
-            }
-            return router;
-        }
+    private enum Policy {
+        oauth2    (0),
+        basic     (50),
+        anonymous (100);
 
-        private static ConnectionFactory createLdapConnectionFactory(final JsonValue configuration) {
-            final String ldapFactoryName = configuration.get("servlet").get("ldapConnectionFactory").asString();
-            if (ldapFactoryName != null) {
-                return configureConnectionFactory(
-                        configuration.get("ldapConnectionFactories").required(), ldapFactoryName);
-            }
-            return null;
-        }
+        private final int priority;
 
-        @Override
-        public void close() {
-            closeSilently(ldapConnectionFactory);
-        }
-
-        @Override
-        public Promise<Response, NeverThrowsException> handle(final Context context, final Request request) {
-            return delegate.handle(context, request);
+        Policy(int priority) {
+            this.priority = priority;
         }
     }
 
-    private final URL configurationUrl;
-    private HttpHandler handler;
-    private HttpAuthenticationFilter filter;
+    private enum BindStrategy {
+        simple, search, sasl_plain
+    }
 
     /**
-     * Default constructor called by the HTTP Framework which will use the
-     * default configuration file location.
+     * Default constructor called by the HTTP Framework which will use the default configuration file location.
      */
     public Rest2LDAPHttpApplication() {
         this.configurationUrl = getClass().getResource("/opendj-rest2ldap-config.json");
+        this.schema = Schema.getDefaultSchema();
     }
 
     /**
      * Creates a new Rest2LDAP HTTP application using the provided configuration URL.
      *
      * @param configurationURL
-     *            The URL to the JSON configuration file.
+     *            The URL to the JSON configuration file
+     * @param schema
+     *            The {@link Schema} used to perform DN validations
      */
-    public Rest2LDAPHttpApplication(final URL configurationURL) {
-        Reject.ifNull(configurationURL, "The configuration URL must not be null");
-        this.configurationUrl = configurationURL;
+    public Rest2LDAPHttpApplication(final URL configurationURL, final Schema schema) {
+        this.configurationUrl = checkNotNull(configurationURL, "configurationURL cannot be null");
+        this.schema = checkNotNull(schema, "schema cannot be null");
+    }
+
+    @Override
+    public final Handler start() throws HttpApplicationException {
+        try {
+            final JsonValue configuration = readJson(configurationUrl);
+            configureConnectionFactories(configuration.get("ldapConnectionFactories"));
+            return Handlers.chainOf(
+                    CrestHttp.newHttpHandler(configureRest2Ldap(configuration)),
+                    newAuthorizationFilter(configuration.get("authorization").required()));
+        } catch (final Exception e) {
+            // TODO i18n, once supported in opendj-rest2ldap
+            final String errorMsg = "Unable to start Rest2Ldap Http Application";
+            LOG.error(errorMsg, e);
+            stop();
+            throw new HttpApplicationException(errorMsg, e);
+        }
     }
 
     private static JsonValue readJson(final URL resource) throws IOException {
@@ -136,19 +147,20 @@
         }
     }
 
-    @Override
-    public Handler start() throws HttpApplicationException {
-        try {
-            final JsonValue configuration = readJson(configurationUrl);
-            handler = new HttpHandler(configuration);
-            filter = new HttpAuthenticationFilter(configuration);
-            return Handlers.chainOf(handler, filter);
-        } catch (final Exception e) {
-            // TODO i18n, once supported in opendj-rest2ldap
-            final String errorMsg = "Unable to start Rest2Ldap Http Application";
-            LOG.error(errorMsg, e);
-            stop();
-            throw new HttpApplicationException(errorMsg, e);
+    private static RequestHandler configureRest2Ldap(final JsonValue configuration) {
+        final JsonValue mappings = configuration.get("mappings").required();
+        final Router router = new Router();
+        for (final String mappingUrl : mappings.keys()) {
+            final JsonValue mapping = mappings.get(mappingUrl);
+            router.addRoute(Router.uriTemplate(mappingUrl), Rest2LDAP.builder().configureMapping(mapping).build());
+        }
+        return router;
+    }
+
+    private void configureConnectionFactories(final JsonValue config) {
+        connectionFactories.clear();
+        for (String name : config.keys()) {
+            connectionFactories.put(name, configureConnectionFactory(config, name));
         }
     }
 
@@ -160,8 +172,236 @@
 
     @Override
     public void stop() {
-        closeSilently(handler, filter);
-        handler = null;
-        filter = null;
+        for (ConnectionFactory factory : connectionFactories.values()) {
+            closeSilently(factory);
+        }
+        connectionFactories.clear();
+    }
+
+    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) {
+        return new Filter() {
+            @Override
+            public Promise<Response, NeverThrowsException> filter(Context context, Request request, Handler next) {
+                return next.handle(new SecurityContext(context, authenticationId, authorization), request);
+            }
+        };
+    }
+
+    /**
+     * Create 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);
+    }
+
+    /**
+     * Get a {@link ConnectionFactory} from its name.
+     *
+     * @param name
+     *            Name of the {@link ConnectionFactory} as specified in the configuration
+     * @return The associated {@link ConnectionFactory} or null if none can be found
+     */
+    protected ConnectionFactory getConnectionFactory(final String name) {
+        return connectionFactories.get(name);
+    }
+
+    private ConditionalFilter buildBasicFilter(final JsonValue config) {
+        final String bind = config.get("bind").required().asString();
+        final BindStrategy strategy = BindStrategy.valueOf(bind.toLowerCase().replace('-', '_'));
+        return newBasicAuthenticationFilter(buildBindStrategy(strategy, config.get(bind).required()),
+                config.get("supportAltAuthentication").defaultTo(Boolean.FALSE).asBoolean()
+                        ? new CustomHeaderExtractor(
+                                config.get("altAuthenticationUsernameHeader").required().asString(),
+                                config.get("altAuthenticationPasswordHeader").required().asString())
+                        : HttpBasicExtractor.INSTANCE,
+                config.get("reuseAuthenticatedConnection").defaultTo(Boolean.FALSE).asBoolean());
+    }
+
+    /**
+     * Get 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);
+    }
+
+    private AuthenticationStrategy buildBindStrategy(final BindStrategy strategy, final JsonValue config) {
+        switch (strategy) {
+        case simple:
+            return buildSimpleBindStrategy(config);
+        case search:
+            return buildSearchThenBindStrategy(config);
+        case sasl_plain:
+            return buildSASLBindStrategy(config);
+        default:
+            throw new IllegalArgumentException("Unsupported strategy '" + strategy + "'");
+        }
+    }
+
+    private AuthenticationStrategy buildSimpleBindStrategy(final JsonValue config) {
+        return newSimpleBindStrategy(getConnectionFactory(config.get("ldapConnectionFactory")
+                                                                .defaultTo(DEFAULT_BIND_FACTORY).asString()),
+                                     config.get("bindDNTemplate").defaultTo("%s").asString(),
+                                     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);
+    }
+
+    private AuthenticationStrategy buildSearchThenBindStrategy(JsonValue config) {
+        return newSearchThenBindStrategy(
+                getConnectionFactory(
+                        config.get("searchLDAPConnectionFactory").defaultTo(DEFAULT_ROOT_FACTORY).asString()),
+                getConnectionFactory(
+                        config.get("bindLDAPConnectionFactory").defaultTo(DEFAULT_BIND_FACTORY).asString()),
+                DN.valueOf(config.get("baseDN").required().asString(), schema),
+                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/SimpleAttributeMapper.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
index 0035d55..b6d3d4a 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012-2015 ForgeRock AS.
+ * Copyright 2012-2016 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -26,6 +26,7 @@
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.AttributeDescription;
 import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.util.Function;
@@ -114,7 +115,7 @@
     }
 
     @Override
-    Promise<Filter, ResourceException> getLDAPFilter(final RequestState requestState, final JsonPointer path,
+    Promise<Filter, ResourceException> getLDAPFilter(final Connection connection, final JsonPointer path,
             final JsonPointer subPath, final FilterType type, final String operator, final Object valueAssertion) {
         if (subPath.isEmpty()) {
             try {
@@ -136,7 +137,7 @@
 
     @Override
     Promise<Attribute, ResourceException> getNewLDAPAttributes(
-            final RequestState requestState, final JsonPointer path, final List<Object> newValues) {
+            final Connection connection, final JsonPointer path, final List<Object> newValues) {
         try {
             return newResultPromise(jsonToAttribute(newValues, ldapAttributeName, encoder()));
         } catch (final Exception ex) {
@@ -152,7 +153,7 @@
     }
 
     @Override
-    Promise<JsonValue, ResourceException> read(final RequestState requestState, final JsonPointer path, final Entry e) {
+    Promise<JsonValue, ResourceException> read(final Connection connection, final JsonPointer path, final Entry e) {
         try {
             final Object value;
             if (attributeIsSingleValued()) {
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AbstractAsynchronousConnectionDecorator.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AbstractAsynchronousConnectionDecorator.java
new file mode 100644
index 0000000..88ab33c
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AbstractAsynchronousConnectionDecorator.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.*;
+
+import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionEventListener;
+import org.forgerock.opendj.ldap.IntermediateResponseHandler;
+import org.forgerock.opendj.ldap.LdapPromise;
+import org.forgerock.opendj.ldap.SearchResultHandler;
+import org.forgerock.opendj.ldap.requests.AbandonRequest;
+import org.forgerock.opendj.ldap.requests.AddRequest;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.CompareRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ExtendedRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.requests.UnbindRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.CompareResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.Result;
+
+abstract class AbstractAsynchronousConnectionDecorator extends AbstractAsynchronousConnection {
+
+    protected final Connection delegate;
+
+    AbstractAsynchronousConnectionDecorator(Connection delegate) {
+        this.delegate = checkNotNull(delegate, "delegate cannot be null");
+    }
+
+    @Override
+    public LdapPromise<Void> abandonAsync(AbandonRequest request) {
+        return delegate.abandonAsync(request);
+    }
+
+    @Override
+    public LdapPromise<Result> addAsync(AddRequest request, IntermediateResponseHandler intermediateResponseHandler) {
+        return delegate.addAsync(request, intermediateResponseHandler);
+    }
+
+    @Override
+    public void addConnectionEventListener(ConnectionEventListener listener) {
+        delegate.addConnectionEventListener(listener);
+    }
+
+    @Override
+    public LdapPromise<BindResult> bindAsync(BindRequest request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+        return delegate.bindAsync(request, intermediateResponseHandler);
+    }
+
+    @Override
+    public void close(UnbindRequest request, String reason) {
+        delegate.close(request, reason);
+    }
+
+    @Override
+    public LdapPromise<CompareResult> compareAsync(CompareRequest request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+        return delegate.compareAsync(request, intermediateResponseHandler);
+    }
+
+    @Override
+    public LdapPromise<Result> deleteAsync(DeleteRequest request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+        return delegate.deleteAsync(request, intermediateResponseHandler);
+    }
+
+    @Override
+    public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(ExtendedRequest<R> request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+        return delegate.extendedRequestAsync(request, intermediateResponseHandler);
+    }
+
+    @Override
+    public boolean isClosed() {
+        return delegate.isClosed();
+    }
+
+    @Override
+    public boolean isValid() {
+        return delegate.isValid();
+    }
+
+    @Override
+    public LdapPromise<Result> modifyAsync(ModifyRequest request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+        return delegate.modifyAsync(request, intermediateResponseHandler);
+    }
+
+    @Override
+    public LdapPromise<Result> modifyDNAsync(ModifyDNRequest request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+        return delegate.modifyDNAsync(request, intermediateResponseHandler);
+    }
+
+    @Override
+    public void removeConnectionEventListener(ConnectionEventListener listener) {
+        delegate.removeConnectionEventListener(listener);
+    }
+
+    @Override
+    public LdapPromise<Result> searchAsync(SearchRequest request,
+            IntermediateResponseHandler intermediateResponseHandler, SearchResultHandler entryHandler) {
+        return delegate.searchAsync(request, intermediateResponseHandler, entryHandler);
+    }
+
+    @Override
+    public String toString() {
+        return delegate.toString();
+    }
+}
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
new file mode 100644
index 0000000..9101def
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategy.java
@@ -0,0 +1,44 @@
+/*
+ * 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 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;
+import org.forgerock.util.promise.Promise;
+
+/** Authenticate a user and create a {@link SecurityContext} as a result. */
+public interface AuthenticationStrategy {
+    /**
+     * Authenticate a user.
+     *
+     * @param username
+     *            User to authenticate.
+     * @param password
+     *            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);
+}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplate.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java
similarity index 76%
rename from opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplate.java
rename to opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java
index 0ee94a6..6040d0a 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplate.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java
@@ -11,12 +11,9 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions copyright [year] [name of copyright owner]".
  *
- * Copyright 2013-2015 ForgeRock AS.
+ * Copyright 2013-2016 ForgeRock AS.
  */
-package org.forgerock.opendj.rest2ldap;
-
-import static org.forgerock.opendj.rest2ldap.Utils.i18n;
-import static org.forgerock.opendj.rest2ldap.Utils.isJSONPrimitive;
+package org.forgerock.opendj.rest2ldap.authz;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -25,8 +22,6 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.forgerock.json.resource.ForbiddenException;
-import org.forgerock.json.resource.ResourceException;
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.schema.Schema;
 
@@ -38,23 +33,22 @@
  */
 final class AuthzIdTemplate {
     private static interface Impl {
-        String formatAsAuthzId(AuthzIdTemplate t, Object[] templateVariables, Schema schema)
-                throws ResourceException;
+        String formatAsAuthzId(AuthzIdTemplate t, Object[] templateVariables, Schema schema);
     }
 
     private static final Impl DN_IMPL = new Impl() {
 
         @Override
         public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables,
-                final Schema schema) throws ResourceException {
+                final Schema schema) {
             final String authzId = String.format(Locale.ENGLISH, t.formatString, templateVariables);
             try {
                 // Validate the DN.
                 DN.valueOf(authzId.substring(3), schema);
             } catch (final IllegalArgumentException e) {
-                throw new ForbiddenException(
-                        i18n("The request could not be authorized because the required "
-                                + "security principal was not a valid LDAP DN"));
+                throw new IllegalArgumentException(
+                        "The request could not be authorized because the required security principal "
+                        + "was not a valid LDAP DN");
             }
             return authzId;
         }
@@ -66,7 +60,7 @@
 
         @Override
         public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables,
-                final Schema schema) throws ResourceException {
+                final Schema schema) {
             return "dn:" + DN.format(t.dnFormatString, schema, templateVariables);
         }
 
@@ -78,7 +72,7 @@
 
         @Override
         public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables,
-                final Schema schema) throws ResourceException {
+                final Schema schema) {
             return String.format(Locale.ENGLISH, t.formatString, templateVariables);
         }
 
@@ -90,7 +84,15 @@
     private final Impl pimpl;
     private final String template;
 
-    AuthzIdTemplate(final String template) {
+    /**
+     * Create a new authorization ID template.
+     *
+     * @param template
+     *            Authorization ID template
+     * @throws IllegalArgumentException
+     *             if template doesn't start with "u:" or "dn:"
+     */
+    public AuthzIdTemplate(final String template) {
         if (!template.startsWith("u:") && !template.startsWith("dn:")) {
             throw new IllegalArgumentException("Invalid authorization ID template: " + template);
         }
@@ -120,14 +122,21 @@
         return template;
     }
 
-    String formatAsAuthzId(final Map<String, Object> principals, final Schema schema)
-            throws ResourceException {
+    /**
+     * Return the template with all the variable replaced.
+     *
+     * @param principals
+     *            Value to use to replace the variables.
+     * @param schema
+     *            Schema to perform validation.
+     * @return The template with all the variable replaced.
+     */
+    public String formatAsAuthzId(final Map<String, Object> principals, final Schema schema) {
         final String[] templateVariables = getPrincipalsForFormatting(principals);
         return pimpl.formatAsAuthzId(this, templateVariables, schema);
     }
 
-    private String[] getPrincipalsForFormatting(final Map<String, Object> principals)
-            throws ForbiddenException {
+    private String[] getPrincipalsForFormatting(final Map<String, Object> principals) {
         final String[] values = new String[keys.size()];
         for (int i = 0; i < values.length; i++) {
             final String key = keys.get(i);
@@ -135,15 +144,19 @@
             if (isJSONPrimitive(value)) {
                 values[i] = String.valueOf(value);
             } else if (value != null) {
-                throw new ForbiddenException(i18n(
+                throw new IllegalArgumentException(String.format(
                         "The request could not be authorized because the required "
                                 + "security principal '%s' had an invalid data type", key));
             } else {
-                throw new ForbiddenException(i18n(
+                throw new IllegalArgumentException(String.format(
                         "The request could not be authorized because the required "
                                 + "security principal '%s' could not be determined", key));
             }
         }
         return values;
     }
+
+    static boolean isJSONPrimitive(final Object value) {
+        return value instanceof String || value instanceof Boolean || value instanceof Number;
+    }
 }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/CachedReadConnectionDecorator.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/CachedReadConnectionDecorator.java
new file mode 100644
index 0000000..d355612
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/CachedReadConnectionDecorator.java
@@ -0,0 +1,258 @@
+/*
+ * 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 java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.IntermediateResponseHandler;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LdapPromise;
+import org.forgerock.opendj.ldap.LdapResultHandler;
+import org.forgerock.opendj.ldap.SearchResultHandler;
+import org.forgerock.opendj.ldap.SearchScope;
+import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ExtendedRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.forgerock.opendj.ldap.responses.SearchResultReference;
+
+/**
+ * Cache entries by intercepting the result of base search requests. Entries in cache are automatically evicted if their
+ * DN is involved in a modify/delete operation. This cache is used to prevent multiple read operations on the same DN.
+ * This happens frequently when we have to resolve entry references in a collection.
+ */
+final class CachedReadConnectionDecorator extends AbstractAsynchronousConnectionDecorator {
+
+    @SuppressWarnings("serial")
+    private final Map<DN, CachedRead> cachedReads = new LinkedHashMap<DN, CachedRead>() {
+        private static final int MAX_CACHED_ENTRIES = 32;
+
+        @Override
+        protected boolean removeEldestEntry(final Map.Entry<DN, CachedRead> eldest) {
+            return size() > MAX_CACHED_ENTRIES;
+        }
+    };
+
+    /** A cached read request - see cachedReads for more information. */
+    private static final class CachedRead implements SearchResultHandler, LdapResultHandler<Result> {
+        private SearchResultEntry cachedEntry;
+        private final String cachedFilterString;
+
+        /** Promise of the pending read operation. @GuardedBy("cachedPromiseLatch"). */
+        private LdapPromise<Result> cachedPromise;
+        private final CountDownLatch cachedPromiseLatch = new CountDownLatch(1);
+        private final SearchRequest cachedRequest;
+        private volatile Result cachedResult;
+        private final ConcurrentLinkedQueue<SearchResultHandler> waitingResultHandlers = new ConcurrentLinkedQueue<>();
+
+        CachedRead(final SearchRequest request, final SearchResultHandler resultHandler) {
+            this.cachedRequest = request;
+            this.cachedFilterString = request.getFilter().toString();
+            this.waitingResultHandlers.add(resultHandler);
+        }
+
+        @Override
+        public boolean handleEntry(final SearchResultEntry entry) {
+            cachedEntry = entry;
+            return true;
+        }
+
+        @Override
+        public void handleException(final LdapException exception) {
+            handleResult(exception.getResult());
+        }
+
+        @Override
+        public boolean handleReference(final SearchResultReference reference) {
+            // Ignore - should never happen for a base object search.
+            return true;
+        }
+
+        @Override
+        public void handleResult(final Result result) {
+            cachedResult = result;
+            drainQueue();
+        }
+
+        void addResultHandler(final SearchResultHandler resultHandler) {
+            // Fast path.
+            if (cachedResult != null) {
+                invokeResultHandler(resultHandler);
+                return;
+            }
+            // Enqueue and re-check.
+            waitingResultHandlers.add(resultHandler);
+            if (cachedResult != null) {
+                drainQueue();
+            }
+        }
+
+        LdapPromise<Result> getPromise() {
+            // Perform uninterrupted wait since this method is unlikely to block for a long time.
+            boolean wasInterrupted = false;
+            while (true) {
+                try {
+                    cachedPromiseLatch.await();
+                    if (wasInterrupted) {
+                        Thread.currentThread().interrupt();
+                    }
+                    return cachedPromise;
+                } catch (final InterruptedException e) {
+                    wasInterrupted = true;
+                }
+            }
+        }
+
+        boolean isMatchingRead(final SearchRequest request) {
+            // Cached reads are always base object.
+            return request.getScope().equals(SearchScope.BASE_OBJECT)
+                    // Filters must match.
+                    && request.getFilter().toString().equals(cachedFilterString)
+                    // List of requested attributes must match.
+                    && request.getAttributes().equals(cachedRequest.getAttributes());
+        }
+
+        void setPromise(final LdapPromise<Result> promise) {
+            cachedPromise = promise;
+            cachedPromiseLatch.countDown();
+        }
+
+        private void drainQueue() {
+            SearchResultHandler resultHandler;
+            while ((resultHandler = waitingResultHandlers.poll()) != null) {
+                invokeResultHandler(resultHandler);
+            }
+        }
+
+        private void invokeResultHandler(final SearchResultHandler searchResultHandler) {
+            if (cachedEntry != null) {
+                searchResultHandler.handleEntry(cachedEntry);
+            }
+        }
+    }
+
+    CachedReadConnectionDecorator(Connection delegate) {
+        super(delegate);
+    }
+
+    @Override
+    public LdapPromise<BindResult> bindAsync(BindRequest request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+        /*
+         * Simple brute force implementation in case the bind operation
+         * modifies an entry: clear the cachedReads.
+         */
+        evictAll();
+        return delegate.bindAsync(request, intermediateResponseHandler);
+    }
+
+    private void evictAll() {
+        synchronized (cachedReads) {
+            cachedReads.clear();
+        }
+    }
+
+    @Override
+    public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(ExtendedRequest<R> request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+        /*
+         * Simple brute force implementation in case the extended
+         * operation modifies an entry: clear the cachedReads.
+         */
+        evictAll();
+        return delegate.extendedRequestAsync(request, intermediateResponseHandler);
+    }
+
+    @Override
+    public LdapPromise<Result> modifyAsync(ModifyRequest request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+        evict(request.getName());
+        return delegate.modifyAsync(request, intermediateResponseHandler);
+    }
+
+    private void evict(final DN name) {
+        synchronized (cachedReads) {
+            cachedReads.remove(name);
+        }
+    }
+
+    @Override
+    public LdapPromise<Result> deleteAsync(DeleteRequest request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+        // Simple brute force implementation: clear the cachedReads.
+        if (request.containsControl(SubtreeDeleteRequestControl.OID)) {
+            evictAll();
+        } else {
+            evict(request.getName());
+        }
+        return delegate.deleteAsync(request, intermediateResponseHandler);
+    }
+
+    @Override
+    public LdapPromise<Result> modifyDNAsync(ModifyDNRequest request,
+            IntermediateResponseHandler intermediateResponseHandler) {
+        // Simple brute force implementation: clear the cachedReads.
+        evictAll();
+        return delegate.modifyDNAsync(request, intermediateResponseHandler);
+    }
+
+    @Override
+    public LdapPromise<Result> searchAsync(SearchRequest request,
+            IntermediateResponseHandler intermediateResponseHandler, SearchResultHandler entryHandler) {
+        /*
+         * Don't attempt caching if this search is not a read (base
+         * object), or if the search request passed in an intermediate
+         * response handler.
+         */
+        if (!request.getScope().equals(SearchScope.BASE_OBJECT) || intermediateResponseHandler != null) {
+            return delegate.searchAsync(request, intermediateResponseHandler, entryHandler);
+        }
+
+        // This is a read request and a candidate for caching.
+        final CachedRead cachedRead;
+        synchronized (cachedReads) {
+            cachedRead = cachedReads.get(request.getName());
+        }
+        if (cachedRead != null && cachedRead.isMatchingRead(request)) {
+            // The cached read matches this read request.
+            cachedRead.addResultHandler(entryHandler);
+            return cachedRead.getPromise();
+        } else {
+            // Cache the read, possibly evicting a non-matching cached read.
+            final CachedRead pendingCachedRead = new CachedRead(request, entryHandler);
+            synchronized (cachedReads) {
+                cachedReads.put(request.getName(), pendingCachedRead);
+            }
+            final LdapPromise<Result> promise = delegate
+                    .searchAsync(request, intermediateResponseHandler, pendingCachedRead)
+                    .thenOnResult(pendingCachedRead).thenOnException(pendingCachedRead);
+            pendingCachedRead.setPromise(promise);
+            return promise;
+        }
+    }
+}
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
new file mode 100644
index 0000000..ecb187a
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/DirectConnectionFilter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.Utils.*;
+import static org.forgerock.util.Reject.*;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+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.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.rest2ldap.AuthenticatedConnectionContext;
+import org.forgerock.services.context.Context;
+import org.forgerock.util.AsyncFunction;
+import org.forgerock.util.promise.NeverThrowsException;
+import org.forgerock.util.promise.Promise;
+
+/**
+ * Inject {@link Connection} into a {@link AuthenticatedConnectionContext}.
+ */
+public final class DirectConnectionFilter implements Filter {
+
+    private final ConnectionFactory connectionFactory;
+
+    /**
+     * Create a new {@link DirectConnectionFilter}.
+     *
+     * @param connectionFactory
+     *            The factory used to get the {@link Connection}
+     * @throws NullPointerException
+     *             if connectionFactory is null.
+     */
+    public DirectConnectionFilter(ConnectionFactory connectionFactory) {
+        this.connectionFactory = checkNotNull(connectionFactory, "connectionFactory cannot be null");
+    }
+
+    @Override
+    public Promise<Response, NeverThrowsException> filter(final Context context, final Request request,
+            final Handler next) {
+        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
+        return connectionFactory
+                .getConnectionAsync()
+                .thenAsync(new AsyncFunction<Connection, Response, NeverThrowsException>() {
+                    @Override
+                    public Promise<Response, NeverThrowsException> apply(Connection connection) {
+                        connectionHolder.set(connection);
+                        return next.handle(new AuthenticatedConnectionContext(context, connection), request);
+                    }
+                }, new AsyncFunction<LdapException, Response, NeverThrowsException>() {
+                    @Override
+                    public Promise<Response, NeverThrowsException> apply(LdapException exception) {
+                        return asErrorResponse(exception);
+                    }
+                })
+                .thenFinally(close(connectionHolder));
+    }
+}
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
new file mode 100644
index 0000000..43c1aa8
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilter.java
@@ -0,0 +1,184 @@
+/*
+ * 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.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.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;
+
+/**
+ * Inject a {@link SecurityContext} if the credentials provided in the {@link Request} headers have been successfully
+ * verified.
+ */
+public final class HttpBasicAuthenticationFilter implements ConditionalFilter {
+
+    private final AuthenticationStrategy authenticationStrategy;
+    private final Function<Headers, Pair<String, String>, NeverThrowsException> credentialsExtractor;
+    private final boolean reuseAuthenticatedConnection;
+
+    /**
+     * Create a new HttpBasicAuthenticationFilter.
+     *
+     * @param authenticationStrategy
+     *            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) {
+        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
+    public Promise<Response, NeverThrowsException> filter(final Context context, final Request request,
+            final Handler next) {
+        final Pair<String, String> credentials = credentialsExtractor.apply(request.getHeaders());
+        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)
+                .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>() {
+                    @Override
+                    public Promise<? extends Response, ? extends NeverThrowsException> apply(LdapException exception) {
+                        return asErrorResponse(exception instanceof EntryNotFoundException
+                                ? 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
new file mode 100644
index 0000000..cc448ba
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/OptionalFilter.java
@@ -0,0 +1,71 @@
+/*
+ * 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
new file mode 100644
index 0000000..7cd316c
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2Filter.java
@@ -0,0 +1,223 @@
+/*
+ * 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.ldap.controls.ProxiedAuthV2RequestControl.newControl;
+import static org.forgerock.opendj.rest2ldap.authz.Utils.asErrorResponse;
+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;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+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.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.IntermediateResponseHandler;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.LdapPromise;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.SearchResultHandler;
+import org.forgerock.opendj.ldap.controls.Control;
+import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
+import org.forgerock.opendj.ldap.requests.AddRequest;
+import org.forgerock.opendj.ldap.requests.CompareRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ExtendedRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+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;
+import org.forgerock.util.AsyncFunction;
+import org.forgerock.util.Function;
+import org.forgerock.util.promise.NeverThrowsException;
+import org.forgerock.util.promise.Promise;
+
+/**
+ * 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 {
+
+    private final ConnectionFactory connectionFactory;
+    private final Function<SecurityContext, String, LdapException> authzProvider;
+
+    /**
+     * Create a new ProxyAuthzFilter. The {@link Connection} contained in the injected
+     * {@link AuthenticatedConnectionContext} will use a {@link ProxiedAuthV2RequestControl} to perform authorization.
+     * The authorizationID of this control is computed for each request by resolving the authzTemplate against the
+     * {@link SecurityContext}.
+     *
+     * @param connectionFactory
+     *            Factory used to get the {@link Connection}
+     * @param authzIdProvider
+     *            Function in charge of providing the authzid to use for the ProxiedAuth control
+     * @throws NullPointerException
+     *             If a parameter is null
+     */
+    public ProxiedAuthV2Filter(final ConnectionFactory connectionFactory,
+            final Function<SecurityContext, String, LdapException> authzIdProvider) {
+        this.connectionFactory = checkNotNull(connectionFactory, "connectionFactory cannot be null");
+        this.authzProvider = checkNotNull(authzIdProvider, "authzIdProvider cannot be null");
+    }
+
+    @Override
+    public Promise<Response, NeverThrowsException> filter(final Context context, final Request request,
+            final Handler next) {
+        final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
+        return connectionFactory
+                .getConnectionAsync()
+                .then(new Function<Connection, Connection, LdapException>() {
+                    @Override
+                    public Connection apply(Connection connection) throws LdapException {
+                        connectionHolder.set(connection);
+                        final Connection proxiedConnection = newProxiedConnection(
+                                connection, authzProvider.apply(context.asContext(SecurityContext.class)));
+                        connectionHolder.set(proxiedConnection);
+                        return proxiedConnection;
+                    }
+                })
+                .thenAsync(new AsyncFunction<Connection, Response, NeverThrowsException>() {
+                    @Override
+                    public Promise<Response, NeverThrowsException> apply(Connection connection) {
+                        return next.handle(new AuthenticatedConnectionContext(context, connection), request);
+                    }
+                }, new AsyncFunction<LdapException, Response, NeverThrowsException>() {
+                    @Override
+                    public Promise<Response, NeverThrowsException> apply(LdapException ldapException) {
+                        return asErrorResponse(ldapException);
+                    }
+                })
+                .thenFinally(close(connectionHolder));
+    }
+
+    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;
+
+        ProxiedAuthConnectionDecorator(Connection delegate, Control proxiedAuthzControl) {
+            super(delegate);
+            this.proxiedAuthzControl = proxiedAuthzControl;
+        }
+
+        @Override
+        public LdapPromise<Result> addAsync(AddRequest request,
+                IntermediateResponseHandler intermediateResponseHandler) {
+            return delegate.addAsync(request.addControl(proxiedAuthzControl), intermediateResponseHandler);
+        }
+
+        @Override
+        public LdapPromise<CompareResult> compareAsync(final CompareRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            return delegate.compareAsync(request.addControl(proxiedAuthzControl), intermediateResponseHandler);
+        }
+
+        @Override
+        public LdapPromise<Result> deleteAsync(final DeleteRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            return delegate.deleteAsync(request.addControl(proxiedAuthzControl), intermediateResponseHandler);
+        }
+
+        @Override
+        public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(final ExtendedRequest<R> request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            return delegate.extendedRequestAsync(request.addControl(proxiedAuthzControl),
+                    intermediateResponseHandler);
+        }
+
+        @Override
+        public LdapPromise<Result> modifyAsync(final ModifyRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            return delegate.modifyAsync(request.addControl(proxiedAuthzControl), intermediateResponseHandler);
+        }
+
+        @Override
+        public LdapPromise<Result> modifyDNAsync(final ModifyDNRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler) {
+            return delegate.modifyDNAsync(request.addControl(proxiedAuthzControl), intermediateResponseHandler);
+        }
+
+        @Override
+        public LdapPromise<Result> searchAsync(final SearchRequest request,
+                final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
+            return delegate.searchAsync(request.addControl(proxiedAuthzControl), intermediateResponseHandler,
+                    entryHandler);
+        }
+    }
+}
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
new file mode 100644
index 0000000..11d8e25
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SASLPlainStrategy.java
@@ -0,0 +1,133 @@
+/*
+ * 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.ldap.requests.Requests.newPlainSASLBindRequest;
+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 java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.forgerock.i18n.LocalizedIllegalArgumentException;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.DecodeOptions;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.controls.AuthorizationIdentityRequestControl;
+import org.forgerock.opendj.ldap.controls.AuthorizationIdentityResponseControl;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.schema.Schema;
+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.promise.Promise;
+
+
+/** Bind using a computed DN from a template and the current request/context. */
+public final class SASLPlainStrategy implements AuthenticationStrategy {
+
+    private final ConnectionFactory connectionFactory;
+    private final Function<String, String, LdapException> formatter;
+
+    /**
+     * Create a new SASLPlainStrategy.
+     *
+     * @param connectionFactory
+     *            Factory used to get {@link Connection} receiving the sasl-bind requests
+     * @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.
+     * @throws NullPointerException
+     *             If a parameter is null
+     */
+    public SASLPlainStrategy(final ConnectionFactory connectionFactory, final Schema schema,
+            final String authcIdTemplate) {
+        this.connectionFactory = checkNotNull(connectionFactory, "connectionFactory cannot be null");
+        checkNotNull(schema, "schema cannot be null");
+        checkNotNull(authcIdTemplate, "authcIdTemplate cannot be null");
+        if (authcIdTemplate.startsWith("dn:")) {
+            formatter = new Function<String, String, LdapException>() {
+                @Override
+                public String apply(String value) throws LdapException {
+                    try {
+                        return DN.format(authcIdTemplate, schema, value).toString();
+                    } catch (LocalizedIllegalArgumentException e) {
+                        throw LdapException.newLdapException(ResultCode.INVALID_DN_SYNTAX, e.getMessageObject(), e);
+                    }
+                }
+            };
+        } else {
+            formatter = new Function<String, String, LdapException>() {
+                @Override
+                public String apply(String value) throws LdapException {
+                    return String.format(authcIdTemplate, value);
+                }
+            };
+        }
+    }
+
+    @Override
+    public Promise<SecurityContext, LdapException> authenticate(final String username, final String password,
+            final Context parentContext, final AtomicReference<Connection> authenticateConnectionHolder) {
+        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);
+                    }
+                });
+    }
+
+    private Promise<SecurityContext, LdapException> doSASLPlainBind(final Connection connection,
+            final Context parentContext, final String authzId, final String authcId, final String password) {
+        return connection
+                .bindAsync(newPlainSASLBindRequest(authcId, password.toCharArray())
+                            .addControl(AuthorizationIdentityRequestControl.newControl(true)))
+                .then(new Function<BindResult, SecurityContext, LdapException>() {
+                    @Override
+                    public SecurityContext apply(BindResult result) throws LdapException {
+                        final Map<String, Object> authz = new LinkedHashMap<>(2);
+                        try {
+                            final AuthorizationIdentityResponseControl control =
+                                    result.getControl(AuthorizationIdentityResponseControl.DECODER,
+                                                      new DecodeOptions());
+                            if (control != null) {
+                                final String authzDN = control.getAuthorizationID();
+                                if (authzDN.startsWith("dn:")) {
+                                    authz.put(AUTHZID_DN, authzDN.substring(3));
+                                }
+                            }
+                        } catch (DecodeException e) {
+                            // Just ignore
+                        }
+                        authz.put(AUTHZID_ID, authzId);
+                        return new SecurityContext(parentContext, authcId, authz);
+                    }
+                });
+    }
+}
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
new file mode 100644
index 0000000..c0f1d00
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SearchThenBindStrategy.java
@@ -0,0 +1,101 @@
+/*
+ * 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.ldap.requests.Requests.newSearchRequest;
+import static org.forgerock.opendj.rest2ldap.authz.SimpleBindStrategy.doSimpleBind;
+import static org.forgerock.opendj.rest2ldap.authz.Utils.close;
+import static org.forgerock.util.Reject.checkNotNull;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Filter;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.SearchScope;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.forgerock.services.context.Context;
+import org.forgerock.services.context.SecurityContext;
+import org.forgerock.util.AsyncFunction;
+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 {
+    private final ConnectionFactory searchConnectionFactory;
+    private final ConnectionFactory bindConnectionFactory;
+
+    private final DN baseDN;
+    private final SearchScope searchScope;
+    private final String filterTemplate;
+
+    /**
+     * Create a new SearchThenBindStrategy.
+     *
+     * @param searchConnectionFactory
+     *            Factory to use to perform the search operation
+     * @param bindConnectionFactory
+     *            Factory to use to perform the bind operation
+     * @param baseDN
+     *            BaseDN of the search request
+     * @param searchScope
+     *            Scope of the search request
+     * @param filterTemplate
+     *            Filter of the search request (i.e: (&(uid=%s)(objectClass=inetOrgPerson)
+     * @throws NullPointerException
+     *             If a parameter is null
+     */
+    public SearchThenBindStrategy(ConnectionFactory searchConnectionFactory, ConnectionFactory bindConnectionFactory,
+            DN baseDN, SearchScope searchScope, String filterTemplate) {
+        this.searchConnectionFactory = checkNotNull(searchConnectionFactory, "searchConnectionFactory cannot be null");
+        this.bindConnectionFactory = checkNotNull(bindConnectionFactory, "bindConnectionFactory cannot be null");
+        this.baseDN = checkNotNull(baseDN, "baseDN cannot be null");
+        this.searchScope = checkNotNull(searchScope, "searchScope cannot be null");
+        this.filterTemplate = checkNotNull(filterTemplate, "filterTemplate cannot be null");
+    }
+
+    @Override
+    public Promise<SecurityContext, LdapException> authenticate(final String username, final String password,
+            final Context parentContext, final AtomicReference<Connection> authenticatedConnectionHolder) {
+        final AtomicReference<Connection> searchConnectionHolder = new AtomicReference<>();
+        return searchConnectionFactory
+                .getConnectionAsync()
+                // Search
+                .thenAsync(new AsyncFunction<Connection, SearchResultEntry, LdapException>() {
+                    @Override
+                    public Promise<SearchResultEntry, LdapException> apply(final Connection connection)
+                            throws LdapException {
+                        searchConnectionHolder.set(connection);
+                        return connection.searchSingleEntryAsync(
+                                newSearchRequest(baseDN, searchScope, Filter.format(filterTemplate, username), "1.1"));
+                    }
+                })
+                .thenFinally(close(searchConnectionHolder))
+                // Bind
+                .thenAsync(new AsyncFunction<SearchResultEntry, SecurityContext, LdapException>() {
+                    @Override
+                    public Promise<SecurityContext, LdapException> apply(final SearchResultEntry searchResult)
+                            throws LdapException {
+                        return bindConnectionFactory
+                                .getConnectionAsync()
+                                .thenAsync(
+                                        doSimpleBind(authenticatedConnectionHolder, parentContext, username,
+                                                searchResult.getName(), password));
+                    }
+                });
+    }
+}
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
new file mode 100644
index 0000000..4d77253
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SimpleBindStrategy.java
@@ -0,0 +1,95 @@
+/*
+ * 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.ldap.requests.Requests.newSimpleBindRequest;
+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 java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+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.responses.BindResult;
+import org.forgerock.opendj.ldap.schema.Schema;
+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.promise.Promise;
+
+/** Bind using a computed DN from a template and the current request/context. */
+public final class SimpleBindStrategy implements AuthenticationStrategy {
+
+    private final ConnectionFactory connectionFactory;
+    private final Schema schema;
+    private final String bindDNTemplate;
+
+    /**
+     * Create a new SimpleBindStrategy.
+     *
+     * @param connectionFactory
+     *            Factory used to get {@link Connection} receiving the bind requests
+     * @param schema
+     *            Schema used to validate DN
+     * @param bindDNTemplate
+     *            The template which will be replaced by the authenticating user (i.e:
+     *            uid=%s,ou=People,dc=example,dc=com)
+     * @throws NullPointerException
+     *             If a parameter is null
+     */
+    public SimpleBindStrategy(ConnectionFactory connectionFactory, String bindDNTemplate, Schema schema) {
+        this.connectionFactory = checkNotNull(connectionFactory, "connectionFactory cannot be null");
+        this.bindDNTemplate = checkNotNull(bindDNTemplate, "bindDNTemplate cannot be null");
+        this.schema = checkNotNull(schema, "schema cannot be null");
+    }
+
+    @Override
+    public Promise<SecurityContext, LdapException> authenticate(final String username, final String password,
+            final Context parentContext, final AtomicReference<Connection> authenticatedConnectionHolder) {
+        return connectionFactory
+                .getConnectionAsync()
+                .thenAsync(doSimpleBind(authenticatedConnectionHolder, parentContext, username,
+                           DN.format(bindDNTemplate, schema, username), password));
+    }
+
+    static AsyncFunction<Connection, SecurityContext, LdapException> doSimpleBind(
+            final AtomicReference<Connection> connectionHolder, final Context parentContext, final String username,
+            final DN bindDN, final String password) {
+        return new AsyncFunction<Connection, SecurityContext, LdapException>() {
+            @Override
+            public Promise<SecurityContext, LdapException> apply(Connection connection) throws LdapException {
+                connectionHolder.set(connection);
+                return connection
+                        .bindAsync(newSimpleBindRequest(bindDN.toString(), password.toCharArray()))
+                        .then(new Function<BindResult, SecurityContext, LdapException>() {
+                            @Override
+                            public SecurityContext apply(BindResult result) throws LdapException {
+                                final Map<String, Object> authzid = new LinkedHashMap<>(2);
+                                authzid.put(AUTHZID_DN, bindDN.toString());
+                                authzid.put(AUTHZID_ID, username);
+                                return new SecurityContext(parentContext, username, authzid);
+                            }
+                        });
+            }
+        };
+    }
+}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Utils.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Utils.java
new file mode 100644
index 0000000..a5cf1a8
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Utils.java
@@ -0,0 +1,54 @@
+/*
+ * 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.Rest2LDAP.asResourceException;
+import static org.forgerock.util.Utils.closeSilently;
+
+import java.io.Closeable;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.forgerock.http.protocol.Response;
+import org.forgerock.http.protocol.Status;
+import org.forgerock.json.resource.ResourceException;
+import org.forgerock.util.promise.NeverThrowsException;
+import org.forgerock.util.promise.Promise;
+import org.forgerock.util.promise.Promises;
+
+final class Utils {
+
+    private Utils() { }
+
+    static Runnable close(final AtomicReference<? extends Closeable> holder) {
+        return new Runnable() {
+            @Override
+            public void run() {
+                closeSilently(holder.get());
+            }
+        };
+    }
+
+    static Promise<Response, NeverThrowsException> asErrorResponse(final Throwable t) {
+        final ResourceException e = asResourceException(t);
+        final Response response = new Response()
+                .setStatus(Status.valueOf(e.getCode()))
+                .setEntity(e.toJsonValue().getObject());
+        if (response.getStatus() == Status.UNAUTHORIZED) {
+            response.getHeaders().put("WWW-Authenticate", "Basic");
+        }
+        return Promises.newResultPromise(response);
+    }
+}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/package-info.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/package-info.java
new file mode 100755
index 0000000..487ebfc
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package contains {@link org.forgerock.http.Filter} to authenticate and authorize LDAP connections. Authorization
+ * filter injects a {@link org.forgerock.services.context.SecurityContext} populated with authorization information like
+ * user's id, user's DN or anything else. This {@link org.forgerock.services.context.SecurityContext} can then be used
+ * by {@link org.forgerock.opendj.rest2ldap.authz.ProxiedAuthV2Filter} to inject an
+ * {@link org.forgerock.opendj.rest2ldap.AuthenticatedConnectionContext} containing the
+ * {@link org.forgerock.opendj.ldap.Connection} with user specific privileges.
+ */
+package org.forgerock.opendj.rest2ldap.authz;
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/package-info.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/package-info.java
index e92fb9e..8cbb3a1 100755
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/package-info.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/package-info.java
@@ -11,11 +11,16 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2016 ForgeRock AS.
  */
 
 /**
- * APIs for implementing REST to LDAP gateways.
+ * APIs for implementing REST to LDAP gateways. The API is implemented by
+ * {@link org.forgerock.opendj.rest2ldap.LDAPCollectionResourceProvider} which is using a pre-established
+ * {@link org.forgerock.opendj.ldap.Connection} encapsulated in the
+ * {@link org.forgerock.opendj.rest2ldap.AuthenticatedConnectionContext}. This context is injected by the
+ * {@link org.forgerock.opendj.rest2ldap.authz.ProxiedAuthV2Filter} depending on the
+ * {@link org.forgerock.services.context.SecurityContext} injected by one of the configured authorization filters.
  */
 package org.forgerock.opendj.rest2ldap;
 
diff --git a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java
index 9c9d9c4..da7137b 100644
--- a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions copyright [year] [name of copyright owner]".
  *
- * Copyright 2013-2015 ForgeRock AS.
+ * Copyright 2013-2016 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -53,10 +53,10 @@
 import org.forgerock.json.resource.NotSupportedException;
 import org.forgerock.json.resource.PreconditionFailedException;
 import org.forgerock.json.resource.QueryResponse;
-import org.forgerock.json.resource.Requests;
 import org.forgerock.json.resource.ResourceResponse;
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.IntermediateResponseHandler;
+import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.LdapResultHandler;
 import org.forgerock.opendj.ldap.MemoryBackend;
 import org.forgerock.opendj.ldap.RequestContext;
@@ -77,6 +77,7 @@
 import org.forgerock.opendj.ldap.responses.Result;
 import org.forgerock.opendj.ldif.LDIFEntryReader;
 import org.forgerock.opendj.rest2ldap.Rest2LDAP.Builder;
+import org.forgerock.services.context.Context;
 import org.forgerock.testng.ForgeRockTestCase;
 import org.forgerock.util.query.QueryFilter;
 import org.testng.annotations.Test;
@@ -96,7 +97,7 @@
         final Connection connection = newConnection();
         final List<ResourceResponse> resources = new LinkedList<>();
         final QueryResponse result = connection.query(
-            ctx(), Requests.newQueryRequest("").setQueryFilter(NO_FILTER), resources);
+                newAuthConnectionContext(), newQueryRequest("").setQueryFilter(NO_FILTER), resources);
         assertThat(resources).hasSize(5);
         assertThat(result.getPagedResultsCookie()).isNull();
         assertThat(result.getTotalPagedResults()).isEqualTo(-1);
@@ -106,8 +107,8 @@
     public void testQueryNone() throws Exception {
         final Connection connection = newConnection();
         final List<ResourceResponse> resources = new LinkedList<>();
-        final QueryResponse result = connection.query(
-            ctx(), Requests.newQueryRequest("").setQueryFilter(QueryFilter.<JsonPointer>alwaysFalse()), resources);
+        final QueryResponse result = connection.query(newAuthConnectionContext(),
+                newQueryRequest("").setQueryFilter(QueryFilter.<JsonPointer> alwaysFalse()), resources);
         assertThat(resources).hasSize(0);
         assertThat(result.getPagedResultsCookie()).isNull();
         assertThat(result.getTotalPagedResults()).isEqualTo(-1);
@@ -120,7 +121,7 @@
 
         // Read first page.
         QueryResponse result = connection.query(
-                ctx(), newQueryRequest("").setQueryFilter(NO_FILTER).setPageSize(2), resources);
+                newAuthConnectionContext(), newQueryRequest("").setQueryFilter(NO_FILTER).setPageSize(2), resources);
         assertThat(result.getPagedResultsCookie()).isNotNull();
         assertThat(resources).hasSize(2);
         assertThat(resources.get(0).getId()).isEqualTo("test1");
@@ -130,7 +131,7 @@
         resources.clear();
 
         // Read second page.
-        result = connection.query(ctx(),
+        result = connection.query(newAuthConnectionContext(),
                 newQueryRequest("").setQueryFilter(NO_FILTER).setPageSize(2).setPagedResultsCookie(cookie), resources);
         assertThat(result.getPagedResultsCookie()).isNotNull();
         assertThat(resources).hasSize(2);
@@ -141,7 +142,7 @@
         resources.clear();
 
         // Read third page.
-        result = connection.query(ctx(),
+        result = connection.query(newAuthConnectionContext(),
                 newQueryRequest("").setQueryFilter(NO_FILTER).setPageSize(2).setPagedResultsCookie(cookie), resources);
         assertThat(result.getPagedResultsCookie()).isNull();
         assertThat(resources).hasSize(1);
@@ -152,7 +153,7 @@
     public void testQueryPageResultsIndexed() throws Exception {
         final Connection connection = newConnection();
         final List<ResourceResponse> resources = new ArrayList<>();
-        QueryResponse result = connection.query(ctx(),
+        QueryResponse result = connection.query(newAuthConnectionContext(),
                 newQueryRequest("").setQueryFilter(NO_FILTER).setPageSize(2).setPagedResultsOffset(1), resources);
         assertThat(result.getPagedResultsCookie()).isNotNull();
         assertThat(resources).hasSize(2);
@@ -162,47 +163,53 @@
 
     @Test(expectedExceptions = NotFoundException.class)
     public void testDelete() throws Exception {
+        final Context context = newAuthConnectionContext();
         final Connection connection = newConnection();
-        final ResourceResponse resource = connection.delete(ctx(), newDeleteRequest("/test1"));
+        final ResourceResponse resource = connection.delete(context, newDeleteRequest("/test1"));
         checkResourcesAreEqual(resource, getTestUser1(12345));
-        connection.read(ctx(), newReadRequest("/test1"));
+        connection.read(context, newReadRequest("/test1"));
     }
 
     @Test(expectedExceptions = NotFoundException.class)
     public void testDeleteMVCCMatch() throws Exception {
+        final Context context = newAuthConnectionContext();
         final Connection connection = newConnection();
-        final ResourceResponse resource = connection.delete(ctx(), newDeleteRequest("/test1").setRevision("12345"));
+        final ResourceResponse resource = connection.delete(context, newDeleteRequest("/test1").setRevision("12345"));
         checkResourcesAreEqual(resource, getTestUser1(12345));
-        connection.read(ctx(), newReadRequest("/test1"));
+        connection.read(context, newReadRequest("/test1"));
     }
 
     @Test(expectedExceptions = PreconditionFailedException.class)
     public void testDeleteMVCCNoMatch() throws Exception {
+        final Context context = newAuthConnectionContext();
         final Connection connection = newConnection();
-        connection.delete(ctx(), newDeleteRequest("/test1").setRevision("12346"));
+        connection.delete(context, newDeleteRequest("/test1").setRevision("12346"));
     }
 
     @Test(expectedExceptions = NotFoundException.class)
     public void testDeleteNotFound() throws Exception {
+        final Context context = newAuthConnectionContext();
         final Connection connection = newConnection();
-        connection.delete(ctx(), newDeleteRequest("/missing"));
+        connection.delete(context, newDeleteRequest("/missing"));
     }
 
     @Test
     public void testPatch() throws Exception {
+        final Context context = newAuthConnectionContext();
         final Connection connection = newConnection();
-        final ResourceResponse resource1 =
-                connection.patch(ctx(), newPatchRequest("/test1", add("/name/displayName", "changed")));
+        final ResourceResponse resource1 = connection.patch(context,
+                newPatchRequest("/test1", add("/name/displayName", "changed")));
         checkResourcesAreEqual(resource1, getTestUser1Updated(12345));
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, getTestUser1Updated(12345));
     }
 
     @Test
     public void testPatchEmpty() throws Exception {
         final List<Request> requests = new LinkedList<>();
+        final Context context = newAuthConnectionContext(requests);
         final Connection connection = newConnection(requests);
-        final ResourceResponse resource1 = connection.patch(ctx(), newPatchRequest("/test1"));
+        final ResourceResponse resource1 = connection.patch(context, newPatchRequest("/test1"));
         checkResourcesAreEqual(resource1, getTestUser1(12345));
 
         /*
@@ -212,150 +219,162 @@
         assertThat(requests).hasSize(1);
         assertThat(requests.get(0)).isInstanceOf(SearchRequest.class);
 
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, getTestUser1(12345));
     }
 
     @Test
     public void testPatchAddOptionalAttribute() throws Exception {
+        final Context context = newAuthConnectionContext();
         final Connection connection = newConnection();
         final JsonValue newContent = getTestUser1(12345);
         newContent.put("description", asList("one", "two"));
         final ResourceResponse resource1 =
-                connection.patch(ctx(), newPatchRequest("/test1", add("/description", asList("one",
-                        "two"))));
+                connection.patch(context,
+                                 newPatchRequest("/test1", add("/description", asList("one", "two"))));
         checkResourcesAreEqual(resource1, newContent);
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, newContent);
     }
 
     @Test
     public void testPatchAddOptionalAttributeIndexAppend() throws Exception {
+        final Context context = newAuthConnectionContext();
         final Connection connection = newConnection();
         final JsonValue newContent = getTestUser1(12345);
         newContent.put("description", asList("one", "two"));
         final ResourceResponse resource1 = connection.patch(
-            ctx(), newPatchRequest("/test1", add("/description/-", "one"), add("/description/-", "two")));
+            context, newPatchRequest("/test1", add("/description/-", "one"), add("/description/-", "two")));
         checkResourcesAreEqual(resource1, newContent);
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, newContent);
     }
 
     @Test(expectedExceptions = BadRequestException.class)
     public void testPatchConstantAttribute() throws Exception {
-        newConnection().patch(ctx(), newPatchRequest("/test1", add("/schemas", asList("junk"))));
+        newConnection().patch(newAuthConnectionContext(), newPatchRequest("/test1", add("/schemas", asList("junk"))));
     }
 
     @Test
     public void testPatchDeleteOptionalAttribute() throws Exception {
+        final Context context = newAuthConnectionContext();
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/description", asList("one", "two"))));
-        final ResourceResponse resource1 = connection.patch(ctx(), newPatchRequest("/test1", remove("/description")));
+        connection.patch(context, newPatchRequest("/test1", add("/description", asList("one", "two"))));
+        final ResourceResponse resource1 = connection.patch(context, newPatchRequest("/test1", remove("/description")));
         checkResourcesAreEqual(resource1, getTestUser1(12345));
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, getTestUser1(12345));
     }
 
     @Test
     public void testPatchIncrement() throws Exception {
+        final Context context = newAuthConnectionContext();
         final Connection connection = newConnection();
         final JsonValue newContent = getTestUser1(12345);
         newContent.put("singleNumber", 100);
         newContent.put("multiNumber", asList(200, 300));
 
-        final ResourceResponse resource1 = connection.patch(ctx(), newPatchRequest("/test1",
+        final ResourceResponse resource1 = connection.patch(context, newPatchRequest("/test1",
             add("/singleNumber", 0),
             add("/multiNumber", asList(100, 200)),
             increment("/singleNumber", 100),
             increment("/multiNumber", 100)));
         checkResourcesAreEqual(resource1, newContent);
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, newContent);
     }
 
     @Test(expectedExceptions = BadRequestException.class)
     public void testPatchMissingRequiredAttribute() throws Exception {
-        newConnection().patch(ctx(), newPatchRequest("/test1", remove("/name/surname")));
+        newConnection().patch(newAuthConnectionContext(), newPatchRequest("/test1", remove("/name/surname")));
     }
 
     @Test
     public void testPatchModifyOptionalAttribute() throws Exception {
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/description", asList("one", "two"))));
+        final Context context = newAuthConnectionContext();
+        connection.patch(context, newPatchRequest("/test1", add("/description", asList("one", "two"))));
         final ResourceResponse resource1 =
-                connection.patch(ctx(), newPatchRequest("/test1", add("/description", asList("three"))));
+                connection.patch(context, newPatchRequest("/test1", add("/description", asList("three"))));
         final JsonValue newContent = getTestUser1(12345);
         newContent.put("description", asList("one", "two", "three"));
         checkResourcesAreEqual(resource1, newContent);
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, newContent);
     }
 
     @Test(expectedExceptions = NotSupportedException.class)
     public void testPatchMultiValuedAttributeIndexAppend() throws Exception {
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/description/0", "junk")));
+        connection.patch(newAuthConnectionContext(), newPatchRequest("/test1", add("/description/0", "junk")));
     }
 
     @Test(expectedExceptions = BadRequestException.class)
     public void testPatchMultiValuedAttributeIndexAppendWithList() throws Exception {
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/description/-",
+        connection.patch(newAuthConnectionContext(), newPatchRequest("/test1", add("/description/-",
                 asList("one", "two"))));
     }
 
     @Test(expectedExceptions = BadRequestException.class)
     public void testPatchMultiValuedAttributeWithSingleValue() throws Exception {
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/description", "one")));
+        connection.patch(newAuthConnectionContext(), newPatchRequest("/test1", add("/description", "one")));
     }
 
     @Test
     public void testPatchMVCCMatch() throws Exception {
         final Connection connection = newConnection();
+        final Context context = newAuthConnectionContext();
         final ResourceResponse resource1 = connection.patch(
-            ctx(), newPatchRequest("/test1", add("/name/displayName", "changed")).setRevision("12345"));
+                context, newPatchRequest("/test1", add("/name/displayName", "changed")).setRevision("12345"));
         checkResourcesAreEqual(resource1, getTestUser1Updated(12345));
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, getTestUser1Updated(12345));
     }
 
     @Test(expectedExceptions = PreconditionFailedException.class)
     public void testPatchMVCCNoMatch() throws Exception {
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/name/displayName", "changed")).setRevision("12346"));
+        connection.patch(
+                newAuthConnectionContext(),
+                newPatchRequest("/test1", add("/name/displayName", "changed")).setRevision("12346"));
     }
 
     @Test(expectedExceptions = NotFoundException.class)
     public void testPatchNotFound() throws Exception {
-        newConnection().patch(ctx(), newPatchRequest("/missing", add("/name/displayName", "changed")));
+        newConnection().patch(
+                newAuthConnectionContext(),
+                newPatchRequest("/missing", add("/name/displayName", "changed")));
     }
 
     @Test(expectedExceptions = BadRequestException.class)
     public void testPatchReadOnlyAttribute() throws Exception {
         // Etag is read-only.
-        newConnection().patch(ctx(), newPatchRequest("/test1", add("_rev", "99999")));
+        newConnection().patch(newAuthConnectionContext(), newPatchRequest("/test1", add("_rev", "99999")));
     }
 
     @Test
     public void testPatchReplacePartialObject() throws Exception {
         final Connection connection = newConnection();
+        final Context context = newAuthConnectionContext();
         final JsonValue expected = json(object(
             field("schemas", asList("urn:scim:schemas:core:1.0")),
             field("_id", "test1"),
             field("_rev", "12345"),
             field("name", object(field("displayName", "Humpty"),
                                  field("surname", "Dumpty")))));
-        final ResourceResponse resource1 = connection.patch(ctx(), newPatchRequest("/test1",
+        final ResourceResponse resource1 = connection.patch(context, newPatchRequest("/test1",
             replace("/name", object(field("displayName", "Humpty"), field("surname", "Dumpty")))));
         checkResourcesAreEqual(resource1, expected);
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, expected);
     }
 
     @Test
     public void testPatchReplaceWholeObject() throws Exception {
         final Connection connection = newConnection();
+        final Context context = newAuthConnectionContext();
         final JsonValue newContent = json(object(
             field("name", object(field("displayName", "Humpty"),
                                  field("surname", "Dumpty")))));
@@ -366,70 +385,72 @@
             field("name", object(field("displayName", "Humpty"),
                                  field("surname", "Dumpty")))));
         final ResourceResponse resource1 =
-                connection.patch(ctx(), newPatchRequest("/test1", replace("/", newContent)));
+                connection.patch(context, newPatchRequest("/test1", replace("/", newContent)));
         checkResourcesAreEqual(resource1, expected);
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, expected);
     }
 
     @Test(expectedExceptions = BadRequestException.class)
     public void testPatchSingleValuedAttributeIndexAppend() throws Exception {
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/name/surname/-", "junk")));
+        connection.patch(newAuthConnectionContext(), newPatchRequest("/test1", add("/name/surname/-", "junk")));
     }
 
     @Test(expectedExceptions = NotSupportedException.class)
     public void testPatchSingleValuedAttributeIndexNumber() throws Exception {
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/name/surname/0", "junk")));
+        connection.patch(newAuthConnectionContext(), newPatchRequest("/test1", add("/name/surname/0", "junk")));
     }
 
     @Test(expectedExceptions = BadRequestException.class)
     public void testPatchSingleValuedAttributeWithMultipleValues() throws Exception {
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/name/surname", asList("black",
+        connection.patch(newAuthConnectionContext(), newPatchRequest("/test1", add("/name/surname", asList("black",
                 "white"))));
     }
 
     @Test(expectedExceptions = BadRequestException.class)
     public void testPatchUnknownAttribute() throws Exception {
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/dummy", "junk")));
+        connection.patch(newAuthConnectionContext(), newPatchRequest("/test1", add("/dummy", "junk")));
     }
 
     @Test(expectedExceptions = BadRequestException.class)
     public void testPatchUnknownSubAttribute() throws Exception {
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/description/dummy", "junk")));
+        connection.patch(newAuthConnectionContext(), newPatchRequest("/test1", add("/description/dummy", "junk")));
     }
 
     @Test(expectedExceptions = BadRequestException.class)
     public void testPatchUnknownSubSubAttribute() throws Exception {
         final Connection connection = newConnection();
-        connection.patch(ctx(), newPatchRequest("/test1", add("/description/dummy/dummy", "junk")));
+        connection.patch(newAuthConnectionContext(),
+                         newPatchRequest("/test1", add("/description/dummy/dummy", "junk")));
     }
 
     @Test
     public void testRead() throws Exception {
-        final ResourceResponse resource = newConnection().read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource = newConnection().read(newAuthConnectionContext(), newReadRequest("/test1"));
         checkResourcesAreEqual(resource, getTestUser1(12345));
     }
 
     @Test(expectedExceptions = NotFoundException.class)
     public void testReadNotFound() throws Exception {
-        newConnection().read(ctx(), newReadRequest("/missing"));
+        newConnection().read(newAuthConnectionContext(), newReadRequest("/missing"));
     }
 
     @Test
     public void testReadSelectAllFields() throws Exception {
-        final ResourceResponse resource = newConnection().read(ctx(), newReadRequest("/test1").addField("/"));
+        final ResourceResponse resource = newConnection().read(newAuthConnectionContext(),
+                                                               newReadRequest("/test1").addField("/"));
         checkResourcesAreEqual(resource, getTestUser1(12345));
     }
 
     @Test
     public void testReadSelectPartial() throws Exception {
         final ResourceResponse resource = newConnection().read(
-            ctx(), newReadRequest("/test1").addField("/name/surname"));
+            newAuthConnectionContext(), newReadRequest("/test1").addField("/name/surname"));
         assertThat(resource.getId()).isEqualTo("test1");
         assertThat(resource.getRevision()).isEqualTo("12345");
         assertThat(resource.getContent().get("_id").asString()).isNull();
@@ -442,7 +463,7 @@
     @Test(enabled = false)
     public void testReadSelectPartialInsensitive() throws Exception {
         final ResourceResponse resource = newConnection().read(
-            ctx(), newReadRequest("/test1").addField("/name/SURNAME"));
+            newAuthConnectionContext(), newReadRequest("/test1").addField("/name/SURNAME"));
         assertThat(resource.getId()).isEqualTo("test1");
         assertThat(resource.getRevision()).isEqualTo("12345");
         assertThat(resource.getContent().get("_id").asString()).isNull();
@@ -454,10 +475,11 @@
     @Test
     public void testUpdate() throws Exception {
         final Connection connection = newConnection();
+        final Context context = newAuthConnectionContext();
         final ResourceResponse resource1 = connection.update(
-            ctx(), newUpdateRequest("/test1", getTestUser1Updated(12345)));
+                context, newUpdateRequest("/test1", getTestUser1Updated(12345)));
         checkResourcesAreEqual(resource1, getTestUser1Updated(12345));
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, getTestUser1Updated(12345));
     }
 
@@ -465,7 +487,8 @@
     public void testUpdateNoChange() throws Exception {
         final List<Request> requests = new LinkedList<>();
         final Connection connection = newConnection(requests);
-        final ResourceResponse resource1 = connection.update(ctx(), newUpdateRequest("/test1", getTestUser1(12345)));
+        final Context context = newAuthConnectionContext(requests);
+        final ResourceResponse resource1 = connection.update(context, newUpdateRequest("/test1", getTestUser1(12345)));
 
         // Check that no modify operation was sent
         // (only a single search should be sent in order to get the current resource).
@@ -473,18 +496,19 @@
         assertThat(requests.get(0)).isInstanceOf(SearchRequest.class);
 
         checkResourcesAreEqual(resource1, getTestUser1(12345));
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, getTestUser1(12345));
     }
 
     @Test
     public void testUpdateAddOptionalAttribute() throws Exception {
         final Connection connection = newConnection();
+        final Context context = newAuthConnectionContext();
         final JsonValue newContent = getTestUser1Updated(12345);
         newContent.put("description", asList("one", "two"));
-        final ResourceResponse resource1 = connection.update(ctx(), newUpdateRequest("/test1", newContent));
+        final ResourceResponse resource1 = connection.update(context, newUpdateRequest("/test1", newContent));
         checkResourcesAreEqual(resource1, newContent);
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, newContent);
     }
 
@@ -493,19 +517,20 @@
         final Connection connection = newConnection();
         final JsonValue newContent = getTestUser1Updated(12345);
         newContent.put("schemas", asList("junk"));
-        connection.update(ctx(), newUpdateRequest("/test1", newContent));
+        connection.update(newAuthConnectionContext(), newUpdateRequest("/test1", newContent));
     }
 
     @Test
     public void testUpdateDeleteOptionalAttribute() throws Exception {
         final Connection connection = newConnection();
+        final Context context = newAuthConnectionContext();
         final JsonValue newContent = getTestUser1Updated(12345);
         newContent.put("description", asList("one", "two"));
-        connection.update(ctx(), newUpdateRequest("/test1", newContent));
+        connection.update(newAuthConnectionContext(), newUpdateRequest("/test1", newContent));
         newContent.remove("description");
-        final ResourceResponse resource1 = connection.update(ctx(), newUpdateRequest("/test1", newContent));
+        final ResourceResponse resource1 = connection.update(context, newUpdateRequest("/test1", newContent));
         checkResourcesAreEqual(resource1, newContent);
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, newContent);
     }
 
@@ -514,50 +539,52 @@
         final Connection connection = newConnection();
         final JsonValue newContent = getTestUser1Updated(12345);
         newContent.get("name").remove("surname");
-        connection.update(ctx(), newUpdateRequest("/test1", newContent));
+        connection.update(newAuthConnectionContext(), newUpdateRequest("/test1", newContent));
     }
 
     @Test
     public void testUpdateModifyOptionalAttribute() throws Exception {
         final Connection connection = newConnection();
+        final Context context = newAuthConnectionContext();
         final JsonValue newContent = getTestUser1Updated(12345);
         newContent.put("description", asList("one", "two"));
-        connection.update(ctx(), newUpdateRequest("/test1", newContent));
+        connection.update(newAuthConnectionContext(), newUpdateRequest("/test1", newContent));
         newContent.put("description", asList("three"));
-        final ResourceResponse resource1 = connection.update(ctx(), newUpdateRequest("/test1", newContent));
+        final ResourceResponse resource1 = connection.update(context, newUpdateRequest("/test1", newContent));
         checkResourcesAreEqual(resource1, newContent);
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, newContent);
     }
 
     @Test
     public void testUpdateMVCCMatch() throws Exception {
         final Connection connection = newConnection();
+        final Context context = newAuthConnectionContext();
         final ResourceResponse resource1 =
-                connection.update(ctx(), newUpdateRequest("/test1", getTestUser1Updated(12345)).setRevision("12345"));
+                connection.update(context, newUpdateRequest("/test1", getTestUser1Updated(12345)).setRevision("12345"));
         checkResourcesAreEqual(resource1, getTestUser1Updated(12345));
-        final ResourceResponse resource2 = connection.read(ctx(), newReadRequest("/test1"));
+        final ResourceResponse resource2 = connection.read(context, newReadRequest("/test1"));
         checkResourcesAreEqual(resource2, getTestUser1Updated(12345));
     }
 
     @Test(expectedExceptions = PreconditionFailedException.class)
     public void testUpdateMVCCNoMatch() throws Exception {
         final Connection connection = newConnection();
-        connection.update(ctx(), newUpdateRequest("/test1", getTestUser1Updated(12345))
+        connection.update(newAuthConnectionContext(), newUpdateRequest("/test1", getTestUser1Updated(12345))
                 .setRevision("12346"));
     }
 
     @Test(expectedExceptions = NotFoundException.class)
     public void testUpdateNotFound() throws Exception {
         final Connection connection = newConnection();
-        connection.update(ctx(), newUpdateRequest("/missing", getTestUser1Updated(12345)));
+        connection.update(newAuthConnectionContext(), newUpdateRequest("/missing", getTestUser1Updated(12345)));
     }
 
     @Test(expectedExceptions = BadRequestException.class)
     public void testUpdateReadOnlyAttribute() throws Exception {
         final Connection connection = newConnection();
         // Etag is read-only.
-        connection.update(ctx(), newUpdateRequest("/test1", getTestUser1Updated(99999)));
+        connection.update(newAuthConnectionContext(), newUpdateRequest("/test1", getTestUser1Updated(99999)));
     }
 
     @Test(expectedExceptions = BadRequestException.class)
@@ -565,7 +592,7 @@
         final Connection connection = newConnection();
         final JsonValue newContent = getTestUser1Updated(12345);
         newContent.put("surname", asList("black", "white"));
-        connection.update(ctx(), newUpdateRequest("/test1", newContent));
+        connection.update(newAuthConnectionContext(), newUpdateRequest("/test1", newContent));
     }
 
     @Test(expectedExceptions = BadRequestException.class)
@@ -573,7 +600,7 @@
         final Connection connection = newConnection();
         final JsonValue newContent = getTestUser1Updated(12345);
         newContent.add("dummy", "junk");
-        connection.update(ctx(), newUpdateRequest("/test1", newContent));
+        connection.update(newAuthConnectionContext(), newUpdateRequest("/test1", newContent));
     }
 
     private Connection newConnection() throws IOException {
@@ -586,12 +613,10 @@
 
     private Builder builder(final List<Request> requests) throws IOException {
         return Rest2LDAP.builder()
-                .ldapConnectionFactory(getConnectionFactory(requests))
                 .baseDN("dc=test")
                 .useEtagAttribute()
                 .useClientDNNaming("uid")
                 .readOnUpdatePolicy(ReadOnUpdatePolicy.CONTROLS)
-                .authorizationPolicy(AuthorizationPolicy.NONE)
                 .additionalLDAPAttribute("objectClass", "top", "person")
                 .mapper(object()
                         .attribute("schemas", constant(asList("urn:scim:schemas:core:1.0")))
@@ -618,6 +643,15 @@
                 expectedResource.getContent().getObject());
     }
 
+    private AuthenticatedConnectionContext newAuthConnectionContext() throws LdapException, IOException {
+        return newAuthConnectionContext(new ArrayList<Request>());
+    }
+
+    private AuthenticatedConnectionContext newAuthConnectionContext(List<Request> requests)
+            throws LdapException, IOException {
+        return new AuthenticatedConnectionContext(ctx(), getConnectionFactory(requests).getConnection());
+    }
+
     private ConnectionFactory getConnectionFactory(final List<Request> requests) throws IOException {
         // @formatter:off
         final MemoryBackend backend =
diff --git a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplateTest.java b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplateTest.java
similarity index 96%
rename from opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplateTest.java
rename to opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplateTest.java
index b791036..3d7e099 100644
--- a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplateTest.java
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplateTest.java
@@ -13,14 +13,13 @@
  *
  * Copyright 2013-2016 ForgeRock AS.
  */
-package org.forgerock.opendj.rest2ldap;
+package org.forgerock.opendj.rest2ldap.authz;
 
 import static org.fest.assertions.Assertions.assertThat;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-import org.forgerock.json.resource.ForbiddenException;
 import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.testng.ForgeRockTestCase;
 import org.testng.annotations.DataProvider;
@@ -101,7 +100,7 @@
 
     }
 
-    @Test(dataProvider = "invalidTemplateData", expectedExceptions = ForbiddenException.class)
+    @Test(dataProvider = "invalidTemplateData", expectedExceptions = IllegalArgumentException.class)
     public void testInvalidTemplateData(final String template, Map<String, Object> principals)
             throws Exception {
         new AuthzIdTemplate(template).formatAsAuthzId(principals, Schema.getDefaultSchema());
diff --git a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/DirectConnectionFilterTest.java b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/DirectConnectionFilterTest.java
new file mode 100644
index 0000000..67903c6
--- /dev/null
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/DirectConnectionFilterTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.*;
+import static org.forgerock.opendj.ldap.LdapException.*;
+import static org.forgerock.opendj.ldap.ResultCode.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.concurrent.ExecutionException;
+
+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.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.rest2ldap.AuthenticatedConnectionContext;
+import org.forgerock.services.context.Context;
+import org.forgerock.services.context.RootContext;
+import org.forgerock.testng.ForgeRockTestCase;
+import org.forgerock.util.promise.Promise;
+import org.forgerock.util.promise.Promises;
+import org.mockito.ArgumentCaptor;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@Test
+public class DirectConnectionFilterTest extends ForgeRockTestCase {
+
+    private ConnectionFactory connectionFactory;
+    private Connection connection;
+    private DirectConnectionFilter filter;
+
+    @BeforeMethod
+    public void setUp() {
+        connectionFactory = mock(ConnectionFactory.class);
+        connection = mock(Connection.class);
+        filter = new DirectConnectionFilter(connectionFactory);
+    }
+
+    @Test
+    public void testInjectAuthenticatedConnectionContext() {
+        when(connectionFactory.getConnectionAsync()).thenReturn(newResultPromise(connection));
+        final Handler handler = mock(Handler.class);
+        final ArgumentCaptor<Context> captureContext = ArgumentCaptor.forClass(Context.class);
+        when(handler.handle(captureContext.capture(), any(Request.class)))
+                .thenReturn(Response.newResponsePromise(new Response()));
+
+        filter.filter(new RootContext(), new Request(), handler);
+
+        assertThat(captureContext.getValue().asContext(AuthenticatedConnectionContext.class).getConnection())
+                .isSameAs(connection);
+        verify(connection).close();
+    }
+
+    @Test
+    public void testErrorResponseSentIfCannotGetConnection() throws InterruptedException, ExecutionException {
+        when(connectionFactory.getConnectionAsync()).thenReturn(newExceptionPromise(ADMIN_LIMIT_EXCEEDED));
+
+        final Response response = filter.filter(new RootContext(), new Request(), mock(Handler.class)).get();
+
+        assertThat(response.getStatus()).isEqualTo(Status.PAYLOAD_TOO_LARGE);
+    }
+
+    private static Promise<Connection, LdapException> newResultPromise(Connection connection) {
+        return Promises.<Connection, LdapException> newResultPromise(connection);
+    }
+
+    private static Promise<Connection, LdapException> newExceptionPromise(ResultCode result) {
+        return Promises.<Connection, LdapException> newExceptionPromise(newLdapException(result));
+    }
+}
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
new file mode 100644
index 0000000..a77a9a9
--- /dev/null
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilterTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.*;
+import static org.forgerock.opendj.rest2ldap.authz.HttpBasicAuthenticationFilter.HttpBasicExtractor.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.assertj.core.api.SoftAssertions;
+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.LdapException;
+import org.forgerock.opendj.rest2ldap.authz.HttpBasicAuthenticationFilter.CustomHeaderExtractor;
+import org.forgerock.opendj.rest2ldap.authz.HttpBasicAuthenticationFilter.HttpBasicExtractor;
+import org.forgerock.services.context.Context;
+import org.forgerock.services.context.SecurityContext;
+import org.forgerock.testng.ForgeRockTestCase;
+import org.forgerock.util.Pair;
+import org.forgerock.util.encode.Base64;
+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));
+    }
+
+    @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));
+
+        final HttpBasicAuthenticationFilter filter =
+                new HttpBasicAuthenticationFilter(failureStrategy, HttpBasicExtractor.INSTANCE, false);
+
+        final Response response = filter.filter(null, new Request(), mock(Handler.class)).get();
+        verifyUnauthorizedOutputMessage(response);
+    }
+
+    private void verifyUnauthorizedOutputMessage(Response response) throws IOException {
+        final SoftAssertions softly = new SoftAssertions();
+        softly.assertThat(response.getStatus().getCode()).isEqualTo(401);
+        softly.assertThat(response.getStatus().getReasonPhrase()).isEqualTo("Unauthorized");
+        softly.assertThat(response.getEntity().getJson().toString())
+                .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
new file mode 100644
index 0000000..bfac595
--- /dev/null
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/OptionalFilterTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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
new file mode 100644
index 0000000..66bd7d5
--- /dev/null
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2FilterTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.forgerock.http.Handler;
+import org.forgerock.http.protocol.Request;
+import org.forgerock.http.protocol.Response;
+import org.forgerock.opendj.ldap.AbstractSynchronousConnection;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionEventListener;
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.DecodeOptions;
+import org.forgerock.opendj.ldap.IntermediateResponseHandler;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.SearchResultHandler;
+import org.forgerock.opendj.ldap.SearchScope;
+import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
+import org.forgerock.opendj.ldap.requests.AddRequest;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.CompareRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ExtendedRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.requests.UnbindRequest;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.CompareResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.Responses;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.rest2ldap.AuthenticatedConnectionContext;
+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.promise.Promises;
+import org.mockito.ArgumentCaptor;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@Test
+public class ProxiedAuthV2FilterTest extends ForgeRockTestCase {
+
+    private ProxiedAuthV2Filter filter;
+    private ArgumentCaptor<Context> captureContext;
+    private Handler handler;
+
+    @BeforeMethod
+    public void setUp() {
+        captureContext = ArgumentCaptor.forClass(Context.class);
+        handler = mock(Handler.class);
+    }
+
+    @Test
+    public void testConnectionIsUsingProxiedAuthControlOnRequests() throws LdapException {
+        final ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
+        when(connectionFactory.getConnectionAsync())
+                .thenReturn(Promises.<Connection, LdapException> newResultPromise(new CheckConnection() {
+                    @Override
+                    void verifyRequest(org.forgerock.opendj.ldap.requests.Request request) throws LdapException {
+                        assertThat(request.getControls()).isNotEmpty();
+                        final ProxiedAuthV2RequestControl control;
+                        try {
+                            control = request.getControl(ProxiedAuthV2RequestControl.DECODER, new DecodeOptions());
+                        } catch (DecodeException e) {
+                            throw LdapException.newLdapException(ResultCode.OPERATIONS_ERROR);
+                        }
+                        assertThat(control.getValue())
+                                .isEqualTo(ByteString.valueOfUtf8("dn:uid=whatever,ou=people,dc=com"));
+                    }
+                }));
+        filter = new ProxiedAuthV2Filter(connectionFactory, ProxiedAuthV2Filter.IntrospectionAuthzProvider.INSTANCE);
+
+        final Map<String, Object> authz = new HashMap<>();
+        authz.put(SecurityContext.AUTHZID_DN, "uid=whatever,ou=people,dc=com");
+        final SecurityContext securityContext = new SecurityContext(new RootContext(), "whatever", authz);
+
+        when(handler.handle(captureContext.capture(), any(Request.class)))
+                .thenReturn(Response.newResponsePromise(new Response()));
+        filter.filter(securityContext, new Request(), handler);
+
+        final Connection proxiedConnection = captureContext.getValue().asContext(AuthenticatedConnectionContext.class)
+                .getConnection();
+        proxiedConnection.add(Requests.newAddRequest("cn=test"));
+        proxiedConnection.applyChange(Requests.newChangeRecord("dn: cn=test", "changetype: delete"));
+        proxiedConnection.compare(Requests.newCompareRequest("cn=test", "foo", "bar"));
+        proxiedConnection.search(Requests.newSearchRequest("cn=test", SearchScope.BASE_OBJECT, "(cn=test)", ""));
+        proxiedConnection.modify(Requests.newModifyRequest("cn=test"));
+        proxiedConnection.delete("cn=test");
+        proxiedConnection.deleteSubtree("cn=test");
+        proxiedConnection.extendedRequest("blah", ByteString.empty());
+        proxiedConnection.modifyDN("cn=foo", "cn=bar");
+    }
+
+    private abstract static class CheckConnection extends AbstractSynchronousConnection {
+
+        abstract void verifyRequest(org.forgerock.opendj.ldap.requests.Request request) throws LdapException;
+
+        @Override
+        public Result add(AddRequest request) throws LdapException {
+            verifyRequest(request);
+            return Responses.newResult(ResultCode.SUCCESS);
+        }
+
+        @Override
+        public BindResult bind(BindRequest request) throws LdapException {
+            return Responses.newBindResult(ResultCode.SUCCESS);
+        }
+
+        @Override
+        public void close(UnbindRequest request, String reason) {
+        }
+
+        @Override
+        public CompareResult compare(CompareRequest request) throws LdapException {
+            verifyRequest(request);
+            return Responses.newCompareResult(ResultCode.SUCCESS);
+        }
+
+        @Override
+        public Result delete(DeleteRequest request) throws LdapException {
+            verifyRequest(request);
+            return Responses.newResult(ResultCode.SUCCESS);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <R extends ExtendedResult> R extendedRequest(ExtendedRequest<R> request,
+                IntermediateResponseHandler handler) throws LdapException {
+            verifyRequest(request);
+            return (R) Responses.newGenericExtendedResult(ResultCode.SUCCESS);
+        }
+
+        @Override
+        public Result search(SearchRequest request, SearchResultHandler handler) throws LdapException {
+            verifyRequest(request);
+            return null;
+        }
+
+        @Override
+        public Result modify(ModifyRequest request) throws LdapException {
+            verifyRequest(request);
+            return null;
+        }
+
+        @Override
+        public Result modifyDN(ModifyDNRequest request) throws LdapException {
+            verifyRequest(request);
+            return null;
+        }
+
+        @Override
+        public void addConnectionEventListener(ConnectionEventListener listener) {
+        }
+
+        @Override
+        public boolean isClosed() {
+            return false;
+        }
+
+        @Override
+        public boolean isValid() {
+            return false;
+        }
+
+        @Override
+        public void removeConnectionEventListener(ConnectionEventListener listener) {
+        }
+
+        @Override
+        public String toString() {
+            return null;
+        }
+
+    }
+}
diff --git a/opendj-server-legacy/resource/config/http-config.json b/opendj-server-legacy/resource/config/http-config.json
index 5c6daa4..da8f01b 100644
--- a/opendj-server-legacy/resource/config/http-config.json
+++ b/opendj-server-legacy/resource/config/http-config.json
@@ -1,133 +1,174 @@
 {
-    // The Rest2LDAP authentication filter configuration. The filter will be
-    // disabled if the configuration is not present. Upon successful
-    // authentication the filter will create a security context containing the
-    // following principals:
-    //
-    // "dn" - the DN of the user if known (may not be the case for sasl-plain)
-    // "id" - the username used for authentication.
-    "authenticationFilter" : {
-        // Indicates whether the filter should allow HTTP BASIC authentication.
-        "supportHTTPBasicAuthentication" : true,
+	"authorization": {
+		// 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": ""
+		},
 
-        // Indicates whether the filter should allow alternative authentication
-        // and, if so, which HTTP headers it should obtain the username and
-        // password from.
-        "supportAltAuthentication"        : true,
-        "altAuthenticationUsernameHeader" : "X-OpenIDM-Username",
-        "altAuthenticationPasswordHeader" : "X-OpenIDM-Password",
+	    // Use HTTP Basic authentication's information to bind to the LDAP server.
+	    "basic": {
+	    	// Indicates whether the filter should allow alternative authentication
+	        // and, if so, which HTTP headers it should obtain the username and
+	        // password from.
+	        "supportAltAuthentication"        : true,
+	        "altAuthenticationUsernameHeader" : "X-OpenIDM-Username",
+	        "altAuthenticationPasswordHeader" : "X-OpenIDM-Password",
+	
+            // Define which LDAP bind mechanism to use
+            // Supported mechanisms are "simple", "sasl-plain", "search"
+			"bind": "search",
+						
+			// Bind to the LDAP server using the DN built from the HTTP Basic's username
+			"simple": {
+				// Connection factory used to perform the bind operation.
+				// If missing, "bind" factory will be used.
+			    "ldapConnectionFactory": "bind",
+			
+				// The Bind 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)
+                // If missing, "%s" is used.
+				"bindDNTemplate": "uid=%s,ou=People,dc=example,dc=com"
+			},
+			
+			// Bind to the LDAP server using a SASL Plain request
+			"sasl-plain": {
+				// Connection factory used to perform the bind operation.
+				// If missing, "bind" factory will be used.
+			    "ldapConnectionFactory": "bind",
+			    
+				// Authentication identity template containing a single %s which will be replaced by the authenticating
+                // user's name. (i.e: u:%s)
+				"authcIdTemplate": "u:%s"
+			},
+			
+			// Bind to the LDAP server using the resulting DN of a search request. 
+			"search": {
+				// Connection factory used to perform the search operation.
+				// If missing, "root" factory will be used.
+				"searchLDAPConnectionFactory": "root",
+				
+				// Connection factory used to perform the bind operation.
+				// If missing, "bind" factory will be used.
+				"bindLDAPConnectionFactory": "bind",
+			
+                // The %s filter format parameters will be substituted with the client-provided username, 
+                // using LDAP filter string character escaping.
+                "baseDN"         : "ou=people,dc=example,dc=com",
+                "scope"          : "sub", // Or "one".
+                "filterTemplate" : "(&(uid=%s)(objectClass=inetOrgPerson))"
+			}
+	        // TODO: support for HTTP sessions?
+	    }
+	},
 
-        // The search parameters to use for "search-simple" authentication. The
-        // %s filter format parameters will be substituted with the
-        // client-provided username, using LDAP filter string character escaping.
-        "searchBaseDN"         : "ou=people,dc=example,dc=com",
-        "searchScope"          : "sub", // Or "one".
-        "searchFilterTemplate" : "(&(uid=%s)(objectClass=inetOrgPerson))"
-
-        // TODO: support for HTTP sessions?
-    },
-
-    // The Rest2LDAP Servlet configuration.
-    "servlet" : {
-        // The REST APIs and their LDAP attribute mappings.
-        "mappings" : {
-            "/users" : {
-                "baseDN"              : "ou=people,dc=example,dc=com",
-                "readOnUpdatePolicy"  : "controls",
-                "useSubtreeDelete"    : false,
-                "usePermissiveModify" : true,
-                "etagAttribute"       : "etag",
-                "namingStrategy"      : {
-                    "strategy"    : "clientDNNaming",
-                    "dnAttribute" : "uid"
-                },
-                "additionalLDAPAttributes" : [
-                    {
-                        "type" : "objectClass",
-                        "values" : [
-                            "top",
-                            "person",
-                            "organizationalPerson",
-                            "inetOrgPerson"
-                        ]
-                    }
-                ],
-                "attributes" : {
-                    "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
-                    "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
-                    "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
-                    "userName"    : { "simple"   : { "ldapAttribute" : "mail", "isSingleValued" : true, "writability" : "readOnly" } },
-                    "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true } },
-                    "name"        : { "object"   : {
-                        "givenName"  : { "simple" : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
-                        "familyName" : { "simple" : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } }
-                    } },
-                    "manager"     : { "reference" : {
-                        "ldapAttribute" : "manager",
-                        "baseDN"        : "ou=people,dc=example,dc=com",
-                        "primaryKey"    : "uid",
-                        "mapper"         : { "object" : {
-                            "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
-                            "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
-                        } }
-                    } },
-                    "groups"     : { "reference" : {
-                        "ldapAttribute" : "isMemberOf",
-                        "baseDN"        : "ou=groups,dc=example,dc=com",
-                        "writability"   : "readOnly",
-                        "primaryKey"    : "cn",
-                        "mapper"        : { "object" : {
-                            "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true } }
-                        } }
-                    } },
-                    "contactInformation" : { "object" : {
-                        "telephoneNumber" : { "simple" : { "ldapAttribute" : "telephoneNumber", "isSingleValued" : true } },
-                        "emailAddress"    : { "simple" : { "ldapAttribute" : "mail", "isSingleValued" : true } }
-                    } },
-                    "meta"        : { "object" : {
-                        "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
-                        "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
-                    } }
-                }
+    // The REST APIs and their LDAP attribute mappings.
+    "mappings" : {
+        "/users" : {
+            "baseDN"              : "ou=people,dc=example,dc=com",
+            "readOnUpdatePolicy"  : "controls",
+            "useSubtreeDelete"    : false,
+            "usePermissiveModify" : true,
+            "etagAttribute"       : "etag",
+            "namingStrategy"      : {
+                "strategy"    : "clientDNNaming",
+                "dnAttribute" : "uid"
             },
-            "/groups" : {
-                "baseDN"              : "ou=groups,dc=example,dc=com",
-                "readOnUpdatePolicy"  : "controls",
-                "useSubtreeDelete"    : false,
-                "usePermissiveModify" : true,
-                "etagAttribute"       : "etag",
-                "namingStrategy"      : {
-                    "strategy"    : "clientDNNaming",
-                    "dnAttribute" : "cn"
-                },
-                "additionalLDAPAttributes" : [
-                    {
-                        "type" : "objectClass",
-                        "values" : [
-                            "top",
-                            "groupOfUniqueNames"
-                        ]
-                    }
-                ],
-                "attributes" : {
-                    "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
-                    "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
-                    "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
-                    "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "readOnly" } },
-                    "members"    : { "reference" : {
-                        "ldapAttribute" : "uniqueMember",
-                        "baseDN"        : "dc=example,dc=com",
-                        "primaryKey"    : "uid",
-                        "mapper"        : { "object" : {
-                            "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
-                            "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
-                        } }
-                    } },
-                    "meta"        : { "object" : {
-                        "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
-                        "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
-                    } }
+            "additionalLDAPAttributes" : [
+                {
+                    "type" : "objectClass",
+                    "values" : [
+                        "top",
+                        "person",
+                        "organizationalPerson",
+                        "inetOrgPerson"
+                    ]
                 }
+            ],
+            "attributes" : {
+                "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
+                "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
+                "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
+                "userName"    : { "simple"   : { "ldapAttribute" : "mail", "isSingleValued" : true, "writability" : "readOnly" } },
+                "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true } },
+                "name"        : { "object"   : {
+                    "givenName"  : { "simple" : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
+                    "familyName" : { "simple" : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } }
+                } },
+                "manager"     : { "reference" : {
+                    "ldapAttribute" : "manager",
+                    "baseDN"        : "ou=people,dc=example,dc=com",
+                    "primaryKey"    : "uid",
+                    "mapper"         : { "object" : {
+                        "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
+                        "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
+                    } }
+                } },
+                "groups"     : { "reference" : {
+                    "ldapAttribute" : "isMemberOf",
+                    "baseDN"        : "ou=groups,dc=example,dc=com",
+                    "writability"   : "readOnly",
+                    "primaryKey"    : "cn",
+                    "mapper"        : { "object" : {
+                        "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true } }
+                    } }
+                } },
+                "contactInformation" : { "object" : {
+                    "telephoneNumber" : { "simple" : { "ldapAttribute" : "telephoneNumber", "isSingleValued" : true } },
+                    "emailAddress"    : { "simple" : { "ldapAttribute" : "mail", "isSingleValued" : true } }
+                } },
+                "meta"        : { "object" : {
+                    "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
+                    "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
+                } }
+            }
+        },
+        "/groups" : {
+            "baseDN"              : "ou=groups,dc=example,dc=com",
+            "readOnUpdatePolicy"  : "controls",
+            "useSubtreeDelete"    : false,
+            "usePermissiveModify" : true,
+            "etagAttribute"       : "etag",
+            "namingStrategy"      : {
+                "strategy"    : "clientDNNaming",
+                "dnAttribute" : "cn"
+            },
+            "additionalLDAPAttributes" : [
+                {
+                    "type" : "objectClass",
+                    "values" : [
+                        "top",
+                        "groupOfUniqueNames"
+                    ]
+                }
+            ],
+            "attributes" : {
+                "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
+                "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
+                "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
+                "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "readOnly" } },
+                "members"    : { "reference" : {
+                    "ldapAttribute" : "uniqueMember",
+                    "baseDN"        : "dc=example,dc=com",
+                    "primaryKey"    : "uid",
+                    "mapper"        : { "object" : {
+                        "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
+                        "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
+                    } }
+                } },
+                "meta"        : { "object" : {
+                    "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
+                    "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
+                } }
             }
         }
     }
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java
index 71875e6..eab6f55 100644
--- a/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/adapter/server3x/Adapters.java
@@ -17,6 +17,8 @@
 
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.Collections;
+import java.util.List;
 
 import org.forgerock.opendj.ldap.AbstractSynchronousConnection;
 import org.forgerock.opendj.ldap.ByteString;
@@ -51,17 +53,19 @@
 import org.forgerock.opendj.ldap.responses.Responses;
 import org.forgerock.opendj.ldap.responses.Result;
 import org.forgerock.util.promise.Promise;
+import org.forgerock.util.promise.PromiseImpl;
 import org.opends.server.core.AddOperation;
 import org.opends.server.core.BindOperation;
 import org.opends.server.core.CompareOperation;
 import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ExtendedOperation;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.internal.InternalSearchListener;
 import org.opends.server.protocols.internal.InternalSearchOperation;
 import org.opends.server.protocols.internal.Requests;
-import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.DirectoryException;
+import org.opends.server.types.OperationType;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchResultEntry;
 import org.opends.server.types.SearchResultReference;
@@ -69,7 +73,6 @@
 import static org.forgerock.opendj.adapter.server3x.Converters.*;
 import static org.forgerock.opendj.ldap.ByteString.*;
 import static org.forgerock.opendj.ldap.LdapException.*;
-import static org.forgerock.util.promise.Promises.*;
 
 /** This class provides a connection factory and an adapter for the OpenDJ 2.x server. */
 public final class Adapters {
@@ -90,33 +93,6 @@
     }
 
     /**
-     * Returns a new anonymous connection factory.
-     *
-     * @return A new anonymous connection factory.
-     */
-    public static ConnectionFactory newAnonymousConnectionFactory() {
-        InternalClientConnection icc = new InternalClientConnection(new AuthenticationInfo());
-        return newConnectionFactory(icc);
-    }
-
-    /**
-     * Returns a new connection factory for a specified user.
-     *
-     * @param userDN
-     *            The specified user's DN.
-     * @return a new connection factory.
-     */
-    public static ConnectionFactory newConnectionFactoryForUser(final DN userDN) {
-        InternalClientConnection icc = null;
-        try {
-            icc = new InternalClientConnection(userDN);
-        } catch (DirectoryException e) {
-            throw new IllegalStateException(e.getMessage(), e);
-        }
-        return newConnectionFactory(icc);
-    }
-
-    /**
      * Returns a new connection factory.
      *
      * @param icc
@@ -125,7 +101,6 @@
      */
     public static ConnectionFactory newConnectionFactory(final InternalClientConnection icc) {
         return new ConnectionFactory() {
-
             @Override
             public void close() {
                 // Nothing to do.
@@ -133,8 +108,30 @@
 
             @Override
             public Promise<Connection, LdapException> getConnectionAsync() {
-                // TODO change the path...
-                return newResultPromise(newConnection(icc));
+                final PromiseImpl<Connection, LdapException> promise = PromiseImpl.create();
+                try
+                {
+                  DirectoryServer.getWorkQueue().submitOperation(new AsyncOperation<>(icc, new Runnable()
+                  {
+                    @Override
+                    public void run()
+                    {
+                      try
+                      {
+                        promise.handleResult(getConnection());
+                      }
+                      catch (LdapException e)
+                      {
+                        promise.handleException(e);
+                      }
+                    }
+                  }));
+                }
+                catch (DirectoryException e)
+                {
+                  promise.handleException(LdapException.newLdapException(e.getResultCode()));
+                }
+                return promise;
             }
 
             @Override
@@ -144,214 +141,234 @@
         };
     }
 
-    /**
-     * Returns a new root connection.
-     *
-     * @return A new root connection.
-     */
-    public static Connection newRootConnection() {
-        return newConnection(InternalClientConnection.getRootConnection());
-    }
-
-    /**
-     * Returns a new connection for an anonymous user.
-     *
-     * @return A new connection.
-     */
-    public static Connection newAnonymousConnection() {
-        return newConnection(new InternalClientConnection(new AuthenticationInfo()));
-    }
-
-    /**
-     * Returns a new connection for a specified user.
-     *
-     * @param dn
-     *            The DN of the user.
-     * @return A new connection for a specified user.
-     * @throws LdapException
-     *             If no such object.
-     */
-    public static Connection newConnectionForUser(final DN dn) throws LdapException {
-        try {
-            return newConnection(new InternalClientConnection(dn));
-        } catch (DirectoryException e) {
-            throw newLdapException(Responses.newResult(ResultCode.NO_SUCH_OBJECT));
-        }
-    }
-
     private static Connection newConnection(final InternalClientConnection icc) {
-        return new AbstractSynchronousConnection() {
+      return new AbstractSynchronousConnection() {
 
-            @Override
-            public Result search(final SearchRequest request, final SearchResultHandler handler)
-                    throws LdapException {
-                InternalSearchListener internalSearchListener = new InternalSearchListener() {
+          @Override
+          public Result search(final SearchRequest request, final SearchResultHandler handler)
+                  throws LdapException {
+              InternalSearchListener internalSearchListener = new InternalSearchListener() {
 
-                    @Override
-                    public void handleInternalSearchReference(
-                            InternalSearchOperation searchOperation,
-                            SearchResultReference searchReference) throws DirectoryException {
-                        handler.handleReference(from(searchReference));
-                    }
+                  @Override
+                  public void handleInternalSearchReference(
+                          InternalSearchOperation searchOperation,
+                          SearchResultReference searchReference) throws DirectoryException {
+                      handler.handleReference(from(searchReference));
+                  }
 
-                    @Override
-                    public void handleInternalSearchEntry(InternalSearchOperation searchOperation,
-                            SearchResultEntry searchEntry) throws DirectoryException {
-                        handler.handleEntry(from(searchEntry));
-                    }
-                };
+                  @Override
+                  public void handleInternalSearchEntry(InternalSearchOperation searchOperation,
+                          SearchResultEntry searchEntry) throws DirectoryException {
+                      handler.handleEntry(from(searchEntry));
+                  }
+              };
 
-                final SearchFilter filter = toSearchFilter(request.getFilter());
-                final org.opends.server.protocols.internal.SearchRequest sr =
-                    Requests.newSearchRequest(request.getName(), request.getScope(), filter)
-                        .setDereferenceAliasesPolicy(request.getDereferenceAliasesPolicy())
-                        .setSizeLimit(request.getSizeLimit())
-                        .setTimeLimit(request.getTimeLimit())
-                        .setTypesOnly(request.isTypesOnly())
-                        .addAttribute(request.getAttributes())
-                        .addControl(to(request.getControls()));
-                return getResponseResult(icc.processSearch(sr, internalSearchListener));
-            }
+              final SearchFilter filter = toSearchFilter(request.getFilter());
+              final org.opends.server.protocols.internal.SearchRequest sr =
+                  Requests.newSearchRequest(request.getName(), request.getScope(), filter)
+                      .setDereferenceAliasesPolicy(request.getDereferenceAliasesPolicy())
+                      .setSizeLimit(request.getSizeLimit())
+                      .setTimeLimit(request.getTimeLimit())
+                      .setTypesOnly(request.isTypesOnly())
+                      .addAttribute(request.getAttributes())
+                      .addControl(to(request.getControls()));
+              return getResponseResult(icc.processSearch(sr, internalSearchListener));
+          }
 
-            @Override
-            public void removeConnectionEventListener(ConnectionEventListener listener) {
-                // Internal client connection don't have any connection events.
-            }
+          @Override
+          public void removeConnectionEventListener(ConnectionEventListener listener) {
+              // Internal client connection don't have any connection events.
+          }
 
-            @Override
-            public Result modifyDN(final ModifyDNRequest request) throws LdapException {
-                return getResponseResult(icc.processModifyDN(request));
-            }
+          @Override
+          public Result modifyDN(final ModifyDNRequest request) throws LdapException {
+              return getResponseResult(icc.processModifyDN(request));
+          }
 
-            @Override
-            public Result modify(final ModifyRequest request) throws LdapException {
-                return getResponseResult(icc.processModify(request));
-            }
+          @Override
+          public Result modify(final ModifyRequest request) throws LdapException {
+              return getResponseResult(icc.processModify(request));
+          }
 
-            @Override
-            public boolean isValid() {
-                // Always true.
-                return true;
-            }
+          @Override
+          public boolean isValid() {
+              // Always true.
+              return true;
+          }
 
-            @Override
-            public boolean isClosed() {
-                return false;
-            }
+          @Override
+          public boolean isClosed() {
+              return false;
+          }
 
-            @Override
-            public <R extends ExtendedResult> R extendedRequest(final ExtendedRequest<R> request,
-                    final IntermediateResponseHandler handler) throws LdapException {
+          @Override
+          public <R extends ExtendedResult> R extendedRequest(final ExtendedRequest<R> request,
+                  final IntermediateResponseHandler handler) throws LdapException {
 
-                final ExtendedOperation extendedOperation =
-                        icc.processExtendedOperation(request.getOID(), request.getValue(),
-                                to(request.getControls()));
+              final ExtendedOperation extendedOperation =
+                      icc.processExtendedOperation(request.getOID(), request.getValue(),
+                              to(request.getControls()));
 
-                final Result result = getResponseResult(extendedOperation);
-                final GenericExtendedResult genericExtendedResult =
-                        Responses.newGenericExtendedResult(result.getResultCode())
-                                .setDiagnosticMessage(result.getDiagnosticMessage()).setMatchedDN(
-                                        result.getMatchedDN()).setValue(
-                                        extendedOperation.getResponseValue().toByteString());
-                try {
-                    R extendedResult =
-                            request.getResultDecoder().decodeExtendedResult(genericExtendedResult,
-                                    new DecodeOptions());
-                    for (final Control control : result.getControls()) {
-                        extendedResult.addControl(control);
-                    }
-                    return extendedResult;
+              final Result result = getResponseResult(extendedOperation);
+              final GenericExtendedResult genericExtendedResult =
+                      Responses.newGenericExtendedResult(result.getResultCode())
+                              .setDiagnosticMessage(result.getDiagnosticMessage()).setMatchedDN(
+                                      result.getMatchedDN()).setValue(
+                                      extendedOperation.getResponseValue().toByteString());
+              try {
+                  R extendedResult =
+                          request.getResultDecoder().decodeExtendedResult(genericExtendedResult,
+                                  new DecodeOptions());
+                  for (final Control control : result.getControls()) {
+                      extendedResult.addControl(control);
+                  }
+                  return extendedResult;
 
-                } catch (DecodeException e) {
-                    DN matchedDN = extendedOperation.getMatchedDN();
-                    return request.getResultDecoder().newExtendedErrorResult(
-                            extendedOperation.getResultCode(),
-                            matchedDN != null ? matchedDN.toString() : null,
-                            extendedOperation.getErrorMessage().toString());
-                }
-            }
+              } catch (DecodeException e) {
+                  DN matchedDN = extendedOperation.getMatchedDN();
+                  return request.getResultDecoder().newExtendedErrorResult(
+                          extendedOperation.getResultCode(),
+                          matchedDN != null ? matchedDN.toString() : null,
+                          extendedOperation.getErrorMessage().toString());
+              }
+          }
 
-            @Override
-            public Result delete(final DeleteRequest request) throws LdapException {
-                final DeleteOperation deleteOperation =
-                        icc.processDelete(valueOfObject(request.getName()), to(request.getControls()));
-                return getResponseResult(deleteOperation);
-            }
+          @Override
+          public Result delete(final DeleteRequest request) throws LdapException {
+              final DeleteOperation deleteOperation =
+                      icc.processDelete(valueOfObject(request.getName()), to(request.getControls()));
+              return getResponseResult(deleteOperation);
+          }
 
-            @Override
-            public CompareResult compare(final CompareRequest request) throws LdapException {
-                final CompareOperation compareOperation =
-                        icc.processCompare(valueOfObject(request.getName()),
-                                request.getAttributeDescription().toString(),
-                                request.getAssertionValue(), to(request.getControls()));
+          @Override
+          public CompareResult compare(final CompareRequest request) throws LdapException {
+              final CompareOperation compareOperation =
+                      icc.processCompare(valueOfObject(request.getName()),
+                              request.getAttributeDescription().toString(),
+                              request.getAssertionValue(), to(request.getControls()));
 
-                CompareResult result = Responses.newCompareResult(compareOperation.getResultCode());
-                return getResponseResult(compareOperation, result);
-            }
+              CompareResult result = Responses.newCompareResult(compareOperation.getResultCode());
+              return getResponseResult(compareOperation, result);
+          }
 
-            @Override
-            public void close(final UnbindRequest request, final String reason) {
-                // no implementation in open-ds.
-            }
+          @Override
+          public void close(final UnbindRequest request, final String reason) {
+              // no implementation in open-ds.
+          }
 
-            @Override
-            public BindResult bind(final BindRequest request) throws LdapException {
-                BindOperation bindOperation = null;
-                if (request instanceof SimpleBindRequest) {
-                    bindOperation =
-                            icc.processSimpleBind(valueOfUtf8(request.getName()),
-                                    ByteString.wrap(((SimpleBindRequest) request).getPassword()),
-                                    to(request.getControls()));
-                } else if (request instanceof SASLBindRequest) {
-                    String serverName = null;
-                    try {
-                        serverName = InetAddress.getByName(null).getCanonicalHostName();
-                    } catch (UnknownHostException e) {
-                        // nothing to do.
-                    }
-                    BindClient bindClient = request.createBindClient(serverName);
-                    do {
-                        final GenericBindRequest genericBindRequest = bindClient.nextBindRequest();
-                        bindOperation =
-                                icc.processSASLBind(
-                                        valueOfUtf8(request.getName()),
-                                        ((SASLBindRequest) request).getSASLMechanism(),
-                                        getCredentials(genericBindRequest.getAuthenticationValue()),
-                                        to(request.getControls()));
-                    } while (bindOperation.getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS);
+          @Override
+          public BindResult bind(final BindRequest request) throws LdapException {
+              BindOperation bindOperation = null;
+              if (request instanceof SimpleBindRequest) {
+                  bindOperation =
+                          icc.processSimpleBind(valueOfUtf8(request.getName()),
+                                  ByteString.wrap(((SimpleBindRequest) request).getPassword()),
+                                  to(request.getControls()));
+              } else if (request instanceof SASLBindRequest) {
+                  String serverName = null;
+                  try {
+                      serverName = InetAddress.getByName(null).getCanonicalHostName();
+                  } catch (UnknownHostException e) {
+                      // nothing to do.
+                  }
+                  BindClient bindClient = request.createBindClient(serverName);
+                  do {
+                      final GenericBindRequest genericBindRequest = bindClient.nextBindRequest();
+                      bindOperation =
+                              icc.processSASLBind(
+                                      valueOfUtf8(request.getName()),
+                                      ((SASLBindRequest) request).getSASLMechanism(),
+                                      getCredentials(genericBindRequest.getAuthenticationValue()),
+                                      to(request.getControls()));
+                  } while (bindOperation.getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS);
 
-                    bindClient.dispose();
+                  bindClient.dispose();
 
-                } else { // not supported
-                    throw newLdapException(Responses.newResult(ResultCode.AUTH_METHOD_NOT_SUPPORTED));
-                }
-                BindResult result = Responses.newBindResult(bindOperation.getResultCode());
-                result.setServerSASLCredentials(bindOperation.getSASLCredentials());
+              } else { // not supported
+                  throw newLdapException(Responses.newResult(ResultCode.AUTH_METHOD_NOT_SUPPORTED));
+              }
+              BindResult result = Responses.newBindResult(bindOperation.getResultCode());
+              result.setServerSASLCredentials(bindOperation.getSASLCredentials());
 
-                if (result.isSuccess()) {
-                    return result;
-                } else {
-                    throw newLdapException(result);
-                }
-            }
+              if (result.isSuccess()) {
+                  return result;
+              } else {
+                  throw newLdapException(result);
+              }
+          }
 
-            @Override
-            public void addConnectionEventListener(ConnectionEventListener listener) {
-                // Internal client connection don't have any connection events.
-            }
+          @Override
+          public void addConnectionEventListener(ConnectionEventListener listener) {
+              // Internal client connection don't have any connection events.
+          }
 
-            @Override
-            public Result add(final AddRequest request) throws LdapException {
-                final AddOperation addOperation =
-                        icc.processAdd(valueOfObject(request.getName()), to(request
-                                .getAllAttributes()), to(request.getControls()));
-                return getResponseResult(addOperation);
-            }
+          @Override
+          public Result add(final AddRequest request) throws LdapException {
+              final AddOperation addOperation =
+                      icc.processAdd(valueOfObject(request.getName()), to(request
+                              .getAllAttributes()), to(request.getControls()));
+              return getResponseResult(addOperation);
+          }
 
-            @Override
-            public String toString() {
-                return icc.toString();
-            }
-        };
+          @Override
+          public String toString() {
+              return icc.toString();
+          }
+      };
+  }
+
+  /**
+   * This operation is hack to be able to execute a {@link Runnable} in a
+   * Directory Server's worker thread.
+   */
+  private static final class AsyncOperation<V> extends org.opends.server.types.AbstractOperation
+  {
+    private final Runnable runnable;
+
+    AsyncOperation(InternalClientConnection icc, Runnable runnable)
+    {
+      super(icc, icc.nextOperationID(), icc.nextMessageID(), Collections.<org.opends.server.types.Control> emptyList());
+      this.setInternalOperation(true);
+      this.runnable = runnable;
     }
+
+    @Override
+    public void run()
+    {
+      runnable.run();
+    }
+
+    @Override
+    public OperationType getOperationType()
+    {
+      return null;
+    }
+    @Override
+    public List<org.opends.server.types.Control> getResponseControls()
+    {
+      return Collections.emptyList();
+    }
+    @Override
+    public void addResponseControl(org.opends.server.types.Control control)
+    {
+    }
+    @Override
+    public void removeResponseControl(org.opends.server.types.Control control)
+    {
+    }
+    @Override
+    public DN getProxiedAuthorizationDN()
+    {
+      return null;
+    }
+    @Override
+    public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN)
+    {
+    }
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append(AsyncOperation.class.getSimpleName());
+    }
+  }
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/api/HttpEndpoint.java b/opendj-server-legacy/src/main/java/org/opends/server/api/HttpEndpoint.java
index 8fea4d9..e2336d8 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/api/HttpEndpoint.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/api/HttpEndpoint.java
@@ -24,6 +24,7 @@
 import org.forgerock.http.HttpApplicationException;
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.opendj.server.config.server.HTTPEndpointCfg;
+import org.opends.server.core.ServerContext;
 import org.opends.server.types.InitializationException;
 
 /**
@@ -37,15 +38,21 @@
   /** Configuration of this endpoint. */
   protected final C configuration;
 
+  /** Context of this LDAP server. */
+  protected final ServerContext serverContext;
+
   /**
    * Create a new {@link HttpEndpoint} with the given configuration.
    *
    * @param configuration
-   *          Configuration of this {@link HttpEndpoint}.
+   *          Configuration of this {@link HttpEndpoint}
+   * @param serverContext
+   *          Context of this LDAP server
    */
-  public HttpEndpoint(C configuration)
+  public HttpEndpoint(C configuration, ServerContext serverContext)
   {
     this.configuration = configuration;
+    this.serverContext = serverContext;
   }
 
   /**
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java b/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java
index 7854287..ea44a46 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java
@@ -295,6 +295,9 @@
   /** The configuration handler for the Directory Server. */
   private ConfigurationHandler configurationHandler;
 
+  /** The configuration manager that will handle HTTP endpoints. */
+  private HttpEndpointConfigManager httpEndpointConfigManager;
+
   /** The set of account status notification handlers defined in the server. */
   private ConcurrentMap<DN, AccountStatusNotificationHandler<?>>
                accountStatusNotificationHandlers;
@@ -1554,9 +1557,6 @@
       identityMapperConfigManager = new IdentityMapperConfigManager(serverContext);
       identityMapperConfigManager.initializeIdentityMappers();
 
-      new HttpEndpointConfigManager(httpRouter)
-        .registerTo(serverContext.getServerManagementContext().getRootConfiguration());
-
       initializeRootDNConfigManager();
 
       initializeAuthenticatedUsers();
@@ -1645,6 +1645,9 @@
         admDataSync.synchronize();
       }
 
+      httpEndpointConfigManager = new HttpEndpointConfigManager(serverContext);
+      httpEndpointConfigManager.registerTo(serverContext.getServerManagementContext().getRootConfiguration());
+
       deleteUnnecessaryFiles();
     }
   }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/HttpEndpointConfigManager.java b/opendj-server-legacy/src/main/java/org/opends/server/core/HttpEndpointConfigManager.java
index 200b782..5577b85 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/HttpEndpointConfigManager.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/HttpEndpointConfigManager.java
@@ -49,22 +49,25 @@
  * removals, or modifications to any HTTP endpoints while the server is running.
  */
 public class HttpEndpointConfigManager implements ConfigurationChangeListener<HTTPEndpointCfg>,
-    ConfigurationAddListener<HTTPEndpointCfg>, ConfigurationDeleteListener<HTTPEndpointCfg>
+                                                  ConfigurationAddListener<HTTPEndpointCfg>,
+                                                  ConfigurationDeleteListener<HTTPEndpointCfg>
 {
   private static final LocalizedLogger LOGGER = LocalizedLogger.getLoggerForThisClass();
 
+  private final ServerContext serverContext;
   private final Router router;
   private final Map<DN, HttpApplication> applications;
 
   /**
    * Creates a new instance of this HTTP endpoint config manager.
    *
-   * @param router
-   *          The {@link Router} where to register configured {@link HttpEndpoint}
+   * @param serverContext
+   *          The server context.
    */
-  public HttpEndpointConfigManager(Router router)
+  public HttpEndpointConfigManager(ServerContext serverContext)
   {
-    this.router = router;
+    this.serverContext = serverContext;
+    this.router = serverContext.getHTTPRouter();
     this.applications = new HashMap<>();
   }
 
@@ -197,7 +200,8 @@
       final Class<? extends HttpEndpoint<?>> endpointClass =
           (Class<? extends HttpEndpoint<?>>) HTTPEndpointCfgDefn.getInstance().getJavaClassPropertyDefinition()
               .loadClass(configuration.getJavaClass(), HttpEndpoint.class);
-      return endpointClass.getDeclaredConstructor(configuration.configurationClass()).newInstance(configuration);
+      return endpointClass.getDeclaredConstructor(configuration.configurationClass(), ServerContext.class)
+                          .newInstance(configuration, serverContext);
     }
     catch (Exception e)
     {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/AuthenticationFilter.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/AuthenticationFilter.java
deleted file mode 100644
index 3293058..0000000
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/AuthenticationFilter.java
+++ /dev/null
@@ -1,346 +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 2013-2016 ForgeRock AS.
- */
-package org.opends.server.protocols.http;
-
-import static org.opends.messages.ProtocolMessages.*;
-import static org.opends.server.util.StaticUtils.*;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.text.ParseException;
-
-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.i18n.LocalizableMessage;
-import org.forgerock.i18n.slf4j.LocalizedLogger;
-import org.forgerock.json.resource.ResourceException;
-import org.forgerock.opendj.adapter.server3x.Adapters;
-import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.Filter;
-import org.forgerock.opendj.ldap.LdapException;
-import org.forgerock.opendj.ldap.ResultCode;
-import org.forgerock.opendj.ldap.requests.BindRequest;
-import org.forgerock.opendj.ldap.requests.Requests;
-import org.forgerock.opendj.ldap.requests.SearchRequest;
-import org.forgerock.opendj.ldap.responses.BindResult;
-import org.forgerock.opendj.ldap.responses.SearchResultEntry;
-import org.forgerock.opendj.rest2ldap.AuthenticatedConnectionContext;
-import org.forgerock.opendj.rest2ldap.Rest2LDAP;
-import org.forgerock.services.context.ClientContext;
-import org.forgerock.services.context.Context;
-import org.forgerock.services.context.SecurityContext;
-import org.forgerock.util.AsyncFunction;
-import org.forgerock.util.promise.NeverThrowsException;
-import org.forgerock.util.promise.Promise;
-import org.forgerock.util.promise.Promises;
-import org.opends.server.schema.SchemaConstants;
-import org.opends.server.util.Base64;
-
-/** Servlet {@link Filter} that collects information about client connections. */
-public final class AuthenticationFilter implements org.forgerock.http.Filter, Closeable
-{
-
-  /** HTTP Header sent by the client with HTTP basic authentication. */
-  static final String HTTP_BASIC_AUTH_HEADER = "Authorization";
-
-  /** The tracer object for the debug logger. */
-  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
-
-  /**
-   * Configures how to perform the search for the username prior to
-   * authentication.
-   */
-  private final HTTPAuthenticationConfig authConfig;
-
-  private final boolean authenticationRequired;
-
-  /**
-   * Constructs a new instance of this class.
-   *
-   * @param authenticationConfig
-   *          configures how to perform the search for the username prior to
-   *          authentication
-   * @param authenticationRequired
-   *          If true, only authenticated requests will be accepted.
-   */
-  public AuthenticationFilter(HTTPAuthenticationConfig authenticationConfig, boolean authenticationRequired)
-  {
-    this.authConfig = authenticationConfig;
-    this.authenticationRequired = authenticationRequired;
-  }
-
-  @Override
-  public Promise<Response, NeverThrowsException> filter(Context context, Request request, Handler next)
-  {
-    try
-    {
-      final Connection ldapConnection = context.asContext(LDAPContext.class).getLdapConnectionFactory().getConnection();
-      final String[] userCredentials = extractUsernamePassword(request);
-      if (userCredentials != null && userCredentials.length == 2)
-      {
-        final String userName = userCredentials[0];
-        final String password = userCredentials[1];
-        return Adapters.newRootConnection()
-                       .searchSingleEntryAsync(buildSearchRequest(userName))
-                       .thenAsync(doBindAfterSearch(context, request, next, userName, password, ldapConnection),
-                                  returnErrorAfterFailedSearch(context.asContext(ClientContext.class)));
-      }
-      else if (authenticationRequired)
-      {
-        return authenticationFailure(context.asContext(ClientContext.class));
-      }
-      else
-      {
-        // Use unauthenticated user
-        return doFilter(context, request, next, ldapConnection);
-      }
-    }
-    catch (Exception e)
-    {
-      return asErrorResponse(e, context.asContext(ClientContext.class));
-    }
-  }
-
-  private SearchRequest buildSearchRequest(String userName)
-  {
-    // Use configured rights to find the user DN
-    final Filter filter = Filter.format(authConfig.getSearchFilterTemplate(), userName);
-    return Requests.newSearchRequest(
-        authConfig.getSearchBaseDN(), authConfig.getSearchScope(), filter, SchemaConstants.NO_ATTRIBUTES);
-  }
-
-  private AsyncFunction<SearchResultEntry, Response, NeverThrowsException> doBindAfterSearch(final Context context,
-      final Request request, final Handler next, final String userName, final String password,
-      final Connection connection)
-  {
-    final ClientContext clientContext = context.asContext(ClientContext.class);
-    return new AsyncFunction<SearchResultEntry, Response, NeverThrowsException>()
-    {
-      @Override
-      public Promise<Response, NeverThrowsException> apply(final SearchResultEntry resultEntry)
-      {
-        final DN bindDN = resultEntry.getName();
-
-        if (bindDN == null)
-        {
-          return authenticationFailure(clientContext);
-        }
-
-        final BindRequest bindRequest =
-            Requests.newSimpleBindRequest(bindDN.toString(), password.getBytes(Charset.forName("UTF-8")));
-        return connection.bindAsync(bindRequest)
-                         .thenAsync(doChain(context, request, next, userName, connection),
-                                    returnErrorAfterFailedBind(clientContext));
-      }
-    };
-  }
-
-  private AsyncFunction<BindResult, Response, NeverThrowsException> doChain(final Context context,
-      final Request request, final Handler next, final String userName, final Connection connection)
-  {
-    return new AsyncFunction<BindResult, Response, NeverThrowsException>()
-    {
-      @Override
-      public Promise<Response, NeverThrowsException> apply(BindResult value) throws NeverThrowsException
-      {
-        try
-        {
-          SecurityContext securityContext = new SecurityContext(context, userName, null);
-          return doFilter(securityContext, request, next, connection);
-        }
-        catch (Exception e)
-        {
-          return asErrorResponse(e, context.asContext(ClientContext.class));
-        }
-      }
-    };
-  }
-
-  private Promise<Response, NeverThrowsException> doFilter(
-      final Context context, final Request request, final Handler next, final Connection connection) throws Exception
-  {
-    final Context forwardedContext = new AuthenticatedConnectionContext(context, connection);
-    // Send the request further down the filter chain or pass to servlet
-    return next.handle(forwardedContext, request);
-  }
-
-  private AsyncFunction<? super LdapException, Response, NeverThrowsException> returnErrorAfterFailedSearch(
-      final ClientContext clientContext)
-  {
-    return new AsyncFunction<LdapException, Response, NeverThrowsException>()
-    {
-      @Override
-      public Promise<Response, NeverThrowsException> apply(final LdapException exception)
-      {
-        final ResultCode rc = exception.getResult().getResultCode();
-        if (ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED.equals(rc)
-         || ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED.equals(rc))
-        {
-          // Avoid information leak:
-          // do not hint to the user that it is the username that is invalid
-          return authenticationFailure(clientContext);
-        }
-        else
-        {
-          return asErrorResponse(exception, clientContext);
-        }
-      }
-    };
-  }
-
-  private AsyncFunction<LdapException, Response, NeverThrowsException> returnErrorAfterFailedBind(
-      final ClientContext clientContext)
-  {
-    return new AsyncFunction<LdapException, Response, NeverThrowsException>()
-    {
-      @Override
-      public Promise<Response, NeverThrowsException> apply(final LdapException e)
-      {
-        return asErrorResponse(e, clientContext);
-      }
-    };
-  }
-
-  private Promise<Response, NeverThrowsException> authenticationFailure(final ClientContext clientContext)
-  {
-    return asErrorResponse(ResourceException.getException(401, "Invalid Credentials"), clientContext, false);
-  }
-
-  private Promise<Response, NeverThrowsException> asErrorResponse(final Throwable t, final ClientContext clientContext)
-  {
-    return asErrorResponse(t, clientContext, true);
-  }
-
-  private Promise<Response, NeverThrowsException> asErrorResponse(final Throwable t, final ClientContext clientContext,
-      final boolean logError)
-  {
-    final ResourceException ex = Rest2LDAP.asResourceException(t);
-    LocalizableMessage message = null;
-    if (logError)
-    {
-      logger.traceException(ex);
-      message =
-          INFO_CONNHANDLER_UNABLE_TO_REGISTER_CLIENT.get(clientContext.getRemotePort(), clientContext.getLocalPort(),
-              getExceptionMessage(ex));
-      logger.debug(message);
-    }
-
-    return resourceExceptionToPromise(ex);
-  }
-
-  Promise<Response, NeverThrowsException> resourceExceptionToPromise(final ResourceException e)
-  {
-    final Response response = new Response().setStatus(Status.valueOf(e.getCode()))
-                                            .setEntity(e.toJsonValue().getObject());
-    if (e.getCode() == 401 && authConfig.isBasicAuthenticationSupported())
-    {
-      response.getHeaders().add("WWW-Authenticate", "Basic realm=\"org.forgerock.opendj\"");
-    }
-    return Promises.newResultPromise(response);
-  }
-
-  /**
-   * Extracts the username and password from the request using one of the
-   * enabled authentication mechanism: HTTP Basic authentication or HTTP Custom
-   * headers. If no username and password can be obtained, then send back an
-   * HTTP basic authentication challenge if HTTP basic authentication is
-   * enabled.
-   *
-   * @param request
-   *          the request where to extract the username and password from
-   * @return the array containing the username/password couple if both exist,
-   *         null otherwise
-   * @throws ResourceException
-   *           if any error occur
-   */
-  String[] extractUsernamePassword(Request request) throws ResourceException
-  {
-    // TODO Use session to reduce hits with search + bind?
-    // Use proxied authorization control for session.
-
-    // Security: How can we remove the password held in the request headers?
-    if (authConfig.isCustomHeadersAuthenticationSupported())
-    {
-      final String userName = request.getHeaders().getFirst(authConfig.getCustomHeaderUsername());
-      final String password = request.getHeaders().getFirst(authConfig.getCustomHeaderPassword());
-      if (userName != null && password != null)
-      {
-        return new String[] { userName, password };
-      }
-    }
-
-    if (authConfig.isBasicAuthenticationSupported())
-    {
-      String httpBasicAuthHeader = request.getHeaders().getFirst(HTTP_BASIC_AUTH_HEADER);
-      if (httpBasicAuthHeader != null)
-      {
-        String[] userCredentials = parseUsernamePassword(httpBasicAuthHeader);
-        if (userCredentials != null)
-        {
-          return userCredentials;
-        }
-      }
-    }
-
-    return null;
-  }
-
-  /**
-   * Parses username and password from the authentication header used in HTTP
-   * basic authentication.
-   *
-   * @param authHeader
-   *          the authentication header obtained from the request
-   * @return an array containing the username at index 0 and the password at
-   *         index 1, or null if the header cannot be parsed successfully
-   * @throws ResourceException
-   *           if the base64 password cannot be decoded
-   */
-  String[] parseUsernamePassword(String authHeader) throws ResourceException
-  {
-    if (authHeader != null
-        && (authHeader.startsWith("Basic") || authHeader.startsWith("basic")))
-    {
-      // We received authentication info
-      // Example received header:
-      // "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
-      String base64UserCredentials = authHeader.substring("basic".length() + 1);
-      try
-      {
-        // Example usage of base64:
-        // Base64("Aladdin:open sesame") = "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
-        String userCredentials = new String(Base64.decode(base64UserCredentials));
-        String[] split = userCredentials.split(":");
-        if (split.length == 2)
-        {
-          return split;
-        }
-      }
-      catch (ParseException e)
-      {
-        throw Rest2LDAP.asResourceException(e);
-      }
-    }
-    return null;
-  }
-
-  @Override
-  public void close() throws IOException {}
-}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPAuthenticationConfig.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPAuthenticationConfig.java
deleted file mode 100644
index 3a83dcd..0000000
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPAuthenticationConfig.java
+++ /dev/null
@@ -1,222 +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 2013-2016 ForgeRock AS.
- */
-package org.opends.server.protocols.http;
-
-import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.SearchScope;
-
-/**
- * Class holding the configuration for HTTP authentication. This is extracted
- * from the JSON config file or the config held in LDAP.
- */
-public final class HTTPAuthenticationConfig
-{
-
-  private boolean basicAuthenticationSupported;
-  private boolean customHeadersAuthenticationSupported;
-  private String customHeaderUsername;
-  private String customHeaderPassword;
-  private DN searchBaseDN;
-  private SearchScope searchScope;
-  private String searchFilterTemplate;
-
-  /**
-   * Returns whether HTTP basic authentication is supported.
-   *
-   * @return true if supported, false otherwise
-   */
-  public boolean isBasicAuthenticationSupported()
-  {
-    return basicAuthenticationSupported;
-  }
-
-  /**
-   * Sets whether HTTP basic authentication is supported.
-   *
-   * @param supported
-   *          the supported value
-   */
-  public void setBasicAuthenticationSupported(boolean supported)
-  {
-    this.basicAuthenticationSupported = supported;
-  }
-
-  /**
-   * Returns whether HTTP authentication via custom headers is supported.
-   *
-   * @return true if supported, false otherwise
-   */
-  public boolean isCustomHeadersAuthenticationSupported()
-  {
-    return customHeadersAuthenticationSupported;
-  }
-
-  /**
-   * Sets whether HTTP authentication via custom headers is supported.
-   *
-   * @param supported
-   *          the supported value
-   */
-  public void setCustomHeadersAuthenticationSupported(boolean supported)
-  {
-    this.customHeadersAuthenticationSupported = supported;
-  }
-
-  /**
-   * Returns the expected HTTP header for the username. This setting is only
-   * used when HTTP authentication via custom headers is supported.
-   *
-   * @return the HTTP header for the username
-   */
-  public String getCustomHeaderUsername()
-  {
-    return customHeaderUsername;
-  }
-
-  /**
-   * Sets the expected HTTP header for the username. This setting only takes
-   * effect when HTTP authentication via custom headers is supported.
-   *
-   * @param customHeaderUsername
-   *          the HTTP header for the username
-   */
-  public void setCustomHeaderUsername(String customHeaderUsername)
-  {
-    this.customHeaderUsername = customHeaderUsername;
-  }
-
-  /**
-   * Returns the expected HTTP header for the password. This setting is only
-   * used when HTTP authentication via custom headers is supported.
-   *
-   * @return the HTTP header for the password
-   */
-  public String getCustomHeaderPassword()
-  {
-    return customHeaderPassword;
-  }
-
-  /**
-   * Sets the expected HTTP header for the password. This setting only takes
-   * effect when HTTP authentication via custom headers is supported.
-   *
-   * @param customHeaderPassword
-   *          the HTTP header for the password
-   */
-  public void setCustomHeaderPassword(String customHeaderPassword)
-  {
-    this.customHeaderPassword = customHeaderPassword;
-  }
-
-  /**
-   * Returns the base DN to use when searching the entry corresponding to the
-   * authenticating user.
-   *
-   * @return the base DN to use when searching the authenticating user
-   */
-  public DN getSearchBaseDN()
-  {
-    return searchBaseDN;
-  }
-
-  /**
-   * Sets the base DN to use when searching the entry corresponding to the
-   * authenticating user.
-   *
-   * @param searchBaseDN
-   *          the base DN to use when searching the authenticating user
-   */
-  public void setSearchBaseDN(DN searchBaseDN)
-  {
-    this.searchBaseDN = searchBaseDN;
-  }
-
-  /**
-   * Returns the search scope to use when searching the entry corresponding to
-   * the authenticating user.
-   *
-   * @return the search scope to use when searching the authenticating user
-   */
-  public SearchScope getSearchScope()
-  {
-    return searchScope;
-  }
-
-  /**
-   * Sets the search scope to use when searching the entry corresponding to the
-   * authenticating user.
-   *
-   * @param searchScope
-   *          the search scope to use when searching the authenticating user
-   */
-  public void setSearchScope(SearchScope searchScope)
-  {
-    this.searchScope = searchScope;
-  }
-
-  /**
-   * Returns the search filter template to use when searching the entry
-   * corresponding to the authenticating user.
-   *
-   * @return the search filter template to use when searching the authenticating
-   *         user
-   */
-  public String getSearchFilterTemplate()
-  {
-    return searchFilterTemplate;
-  }
-
-  /**
-   * Sets the search filter template to use when searching the entry
-   * corresponding to the authenticating user.
-   *
-   * @param searchFilterTemplate
-   *          the search filter template to use when searching the
-   *          authenticating user
-   */
-  public void setSearchFilterTemplate(String searchFilterTemplate)
-  {
-    this.searchFilterTemplate = searchFilterTemplate;
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public String toString()
-  {
-    StringBuilder sb = new StringBuilder();
-    sb.append("basicAuth: ");
-    if (!basicAuthenticationSupported)
-    {
-      sb.append("not ");
-    }
-    sb.append("supported, ");
-    sb.append("customHeadersAuth: ");
-    if (customHeadersAuthenticationSupported)
-    {
-      sb.append("usernameHeader=\"").append(customHeaderUsername).append("\",");
-      sb.append("passwordHeader=\"").append(customHeaderPassword).append("\"");
-    }
-    else
-    {
-      sb.append("not supported, ");
-    }
-    sb.append("searchBaseDN: \"").append(searchBaseDN).append("\"");
-    sb.append("searchScope: \"").append(searchScope).append("\"");
-    sb.append("searchFilterTemplate: \"").append(searchFilterTemplate).append(
-        "\"");
-    return sb.toString();
-  }
-}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/LDAPContext.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/LDAPContext.java
index 4c46a36..bb99b76 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/LDAPContext.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/LDAPContext.java
@@ -13,30 +13,64 @@
  *
  * Copyright 2016 ForgeRock AS.
  */
+
 package org.opends.server.protocols.http;
 
-import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.services.context.AbstractContext;
 import org.forgerock.services.context.Context;
 
-/** Context provided by this LDAP server to the embedded {@link org.forgerock.http.HttpApplication}s. */
+/**
+ * Context provided by a Directory Server. It contains a reference to a
+ * {@link InternalConnectionFactory} which can be used to perform direct LDAP
+ * operation on this Directory Server without the network overhead.
+ */
 public final class LDAPContext extends AbstractContext
 {
-  private final ConnectionFactory ldapConnectionFactory;
+  private final InternalConnectionFactory internalConnectionFactory;
 
-  LDAPContext(final Context parent, ConnectionFactory ldapConnectionFactory)
+  /**
+   * Create a new LDAPContext.
+   *
+   * @param parent
+   *          The parent context.
+   * @param internalConnectionFactory
+   *          Internal connection factory of this LDAP server.
+   */
+  public LDAPContext(final Context parent, InternalConnectionFactory internalConnectionFactory)
   {
     super(parent, "LDAP context");
-    this.ldapConnectionFactory = ldapConnectionFactory;
+    this.internalConnectionFactory = internalConnectionFactory;
   }
 
   /**
-   * Get the {@link org.forgerock.opendj.ldap.LDAPConnectionFactory} attached to this context.
+   * Get the {@link InternalConnectionFactory} attached to this context.
    *
-   * @return The {@link org.forgerock.opendj.ldap.LDAPConnectionFactory} attached to this context.
+   * @return The {@link InternalConnectionFactory} attached to this context.
    */
-  public ConnectionFactory getLdapConnectionFactory()
+  public InternalConnectionFactory getInternalConnectionFactory()
   {
-    return ldapConnectionFactory;
+    return internalConnectionFactory;
   }
-}
+
+  /**
+   * An internal connection factory providing direct connection to this Directory
+   * Server without the network overhead.
+   */
+  public interface InternalConnectionFactory
+  {
+    /**
+     * Get a direct {@link Connection} to this Directory Server.
+     *
+     * @param userDN
+     *          DN of the user's used to validate authorization.
+     * @return A direct {@link Connection} to this Directory Server.
+     * @throws LdapException
+     *           If a connection cannot be create (i.e: because the userDN
+     *           doesn't exists).
+     */
+    Connection getConnection(DN userDN) throws LdapException;
+  }
+}
\ No newline at end of file
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/LDAPContextInjectionFilter.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/LDAPContextInjectionFilter.java
index 30d4c99..a750273 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/LDAPContextInjectionFilter.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/LDAPContextInjectionFilter.java
@@ -15,22 +15,23 @@
  */
 package org.opends.server.protocols.http;
 
-import static org.opends.messages.ProtocolMessages.*;
-
 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.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.ResultCode;
 import org.forgerock.services.context.Context;
 import org.forgerock.util.promise.NeverThrowsException;
 import org.forgerock.util.promise.Promise;
-import org.forgerock.util.promise.Promises;
+import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ServerContext;
-import org.opends.server.types.DisconnectReason;
+import org.opends.server.protocols.http.LDAPContext.InternalConnectionFactory;
+import org.opends.server.types.AuthenticationInfo;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
 
 /**
  * Filter injecting the {@link LDAPContext} giving access to
@@ -41,45 +42,59 @@
   private final ServerContext serverContext;
   private final HTTPConnectionHandler httpConnectionHandler;
 
-  LDAPContextInjectionFilter(ServerContext serverContext, HTTPConnectionHandler httpConnectionHandler) {
+  LDAPContextInjectionFilter(ServerContext serverContext, HTTPConnectionHandler httpConnectionHandler)
+  {
     this.serverContext = serverContext;
-    this.httpConnectionHandler= httpConnectionHandler;
+    this.httpConnectionHandler = httpConnectionHandler;
   }
 
   @Override
-  public Promise<Response, NeverThrowsException> filter(Context context, Request request, Handler next)
+  public Promise<Response, NeverThrowsException> filter(final Context context, final Request request,
+      final Handler next)
   {
-    final HTTPClientConnection clientConnection =
-        new HTTPClientConnection(serverContext, httpConnectionHandler, context, request);
-    if (clientConnection.getConnectionID() < 0)
+    final LDAPContext djContext = new LDAPContext(context, new InternalConnectionFactory()
     {
-      clientConnection.disconnect(DisconnectReason.ADMIN_LIMIT_EXCEEDED, true,
-          ERR_CONNHANDLER_REJECTED_BY_SERVER.get());
-      return Promises.newResultPromise(new Response(Status.SERVICE_UNAVAILABLE));
-    }
-
-    final LDAPContext djContext  = new LDAPContext(context, new ConnectionFactory()
-    {
-      private final Connection connection = new SdkConnectionAdapter(clientConnection);
-
       @Override
-      public Promise<Connection, LdapException> getConnectionAsync()
+      public Connection getConnection(DN userDN) throws LdapException
       {
-        return Promises.newResultPromise(connection);
+        final HTTPClientConnection clientConnection =
+            new HTTPClientConnection(serverContext, httpConnectionHandler, context, request);
+        clientConnection.setAuthenticationInfo(getAuthInfoForDN(userDN));
+        if (clientConnection.getConnectionID() < 0)
+        {
+          throw LdapException.newLdapException(ResultCode.ADMIN_LIMIT_EXCEEDED);
+        }
+        httpConnectionHandler.addClientConnection(clientConnection);
+        return new SdkConnectionAdapter(clientConnection);
       }
 
-      @Override
-      public Connection getConnection() throws LdapException
+      private AuthenticationInfo getAuthInfoForDN(DN userDN) throws LdapException
       {
-        return connection;
-      }
-
-      @Override
-      public void close()
-      {
+        if (userDN == null || userDN.isRootDN())
+        {
+          return new AuthenticationInfo();
+        }
+        final DN rootUserDN = DirectoryServer.getActualRootBindDN(userDN);
+        if (rootUserDN != null)
+        {
+          userDN = rootUserDN;
+        }
+        Entry userEntry;
+        try
+        {
+          userEntry = DirectoryServer.getEntry(userDN);
+        }
+        catch (DirectoryException e)
+        {
+          throw LdapException.newLdapException(e.getResultCode());
+        }
+        if (userEntry == null)
+        {
+          throw LdapException.newLdapException(ResultCode.INVALID_CREDENTIALS);
+        }
+        return new AuthenticationInfo(userEntry, DirectoryServer.isRootDN(userDN));
       }
     });
     return next.handle(djContext, request);
   }
-
-}
\ No newline at end of file
+}
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
new file mode 100644
index 0000000..39a75c2
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/InternalProxyAuthzFilter.java
@@ -0,0 +1,126 @@
+/*
+ * 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.opends.server.protocols.http.rest2ldap;
+
+import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
+import static org.forgerock.util.Reject.checkNotNull;
+import static org.forgerock.util.Utils.closeSilently;
+
+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.i18n.LocalizedIllegalArgumentException;
+import org.forgerock.json.resource.ResourceException;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.LdapException;
+import org.forgerock.opendj.ldap.ResultCode;
+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;
+import org.forgerock.util.Function;
+import org.forgerock.util.promise.NeverThrowsException;
+import org.forgerock.util.promise.Promise;
+import org.forgerock.util.promise.Promises;
+import org.opends.server.api.IdentityMapper;
+import org.opends.server.protocols.http.LDAPContext;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+
+/**
+ * Authorization proxy using internal connection's capabilities as an optimized
+ * alternative to {@link ProxiedAuthV2Control}. This proxy creates an
+ * {@link AuthenticatedConnectionContext} for the current request using its
+ * {@link SecurityContext}.
+ */
+final class InternalProxyAuthzFilter implements Filter
+{
+  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)
+  {
+    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
+  public final Promise<Response, NeverThrowsException> filter(Context context, Request request, Handler next)
+  {
+    final SecurityContext securityContext = context.asContext(SecurityContext.class);
+    final LDAPContext ldapContext = context.asContext(LDAPContext.class);
+    Connection tmp = null;
+    try
+    {
+      tmp = ldapContext.getInternalConnectionFactory().getConnection(getUserDN(securityContext));
+    }
+    catch (LdapException | DirectoryException e)
+    {
+      closeSilently(tmp);
+      return asErrorResponse(e);
+    }
+    final Connection authConnection = tmp;
+    return next.handle(new AuthenticatedConnectionContext(context, authConnection), request)
+               .thenFinally(new Runnable()
+               {
+                 @Override
+                 public void run()
+                 {
+                   closeSilently(authConnection);
+                 }
+               });
+  }
+
+  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:"))
+    {
+      try
+      {
+        return DN.valueOf(authzId.substring(3), schema);
+      }
+      catch (LocalizedIllegalArgumentException e)
+      {
+        throw LdapException.newLdapException(ResultCode.INVALID_DN_SYNTAX, e);
+      }
+    }
+    throw LdapException.newLdapException(ResultCode.AUTHORIZATION_DENIED);
+  }
+
+  static Promise<Response, NeverThrowsException> asErrorResponse(final Throwable t)
+  {
+    final ResourceException e = asResourceException(t);
+    final Response response =
+        new Response().setStatus(Status.valueOf(e.getCode())).setEntity(e.toJsonValue().getObject());
+    return Promises.newResultPromise(response);
+  }
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEmbeddedHttpApplication.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEmbeddedHttpApplication.java
deleted file mode 100644
index 79100ae..0000000
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEmbeddedHttpApplication.java
+++ /dev/null
@@ -1,182 +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 2015-2016 ForgeRock AS.
- */
-package org.opends.server.protocols.http.rest2ldap;
-
-import static org.forgerock.http.util.Json.*;
-import static org.opends.messages.ProtocolMessages.*;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-import org.forgerock.http.Handler;
-import org.forgerock.http.HttpApplication;
-import org.forgerock.http.HttpApplicationException;
-import org.forgerock.http.handler.Handlers;
-import org.forgerock.http.io.Buffer;
-import org.forgerock.http.protocol.Request;
-import org.forgerock.http.protocol.Response;
-import org.forgerock.json.JsonValue;
-import org.forgerock.json.resource.CollectionResourceProvider;
-import org.forgerock.json.resource.ConnectionFactory;
-import org.forgerock.json.resource.RequestHandler;
-import org.forgerock.json.resource.ResourceException;
-import org.forgerock.json.resource.Resources;
-import org.forgerock.json.resource.Router;
-import org.forgerock.json.resource.http.CrestHttp;
-import org.forgerock.json.resource.http.HttpContextFactory;
-import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.SearchScope;
-import org.forgerock.opendj.rest2ldap.AuthorizationPolicy;
-import org.forgerock.opendj.rest2ldap.Rest2LDAP;
-import org.forgerock.services.context.Context;
-import org.forgerock.util.Factory;
-import org.forgerock.util.promise.NeverThrowsException;
-import org.forgerock.util.promise.Promise;
-import org.opends.server.protocols.http.AuthenticationFilter;
-import org.opends.server.protocols.http.HTTPAuthenticationConfig;
-
-/** Entry point of the Rest2Ldap application when used in embedded mode. */
-final class Rest2LdapEmbeddedHttpApplication implements HttpApplication
-{
-  /**
-   * Http Handler re-using the pre-established internal LDAP connection.
-   *
-   * @see AuthenticationFilter
-   */
-  private static final class Rest2LdapHandler implements Handler
-  {
-    private final Handler delegate;
-
-    /**
-     * Build a new {@code LdapHttpHandler}.
-     *
-     * @param configuration
-     *            The configuration which will be used to set
-     *            the connection and the mappings to the OpenDJ server.
-     */
-    public Rest2LdapHandler(final JsonValue configuration)
-    {
-      final ConnectionFactory connectionFactory = Resources.newInternalConnectionFactory(createRouter(configuration));
-      delegate = CrestHttp.newHttpHandler(connectionFactory, new HttpContextFactory()
-      {
-        @Override
-        public Context createContext(Context parentContext, Request request) throws ResourceException
-        {
-          return parentContext;
-        }
-      });
-    }
-
-    private RequestHandler createRouter(final JsonValue configuration)
-    {
-      final JsonValue mappings = configuration.get("servlet").get("mappings").required();
-      final Router router = new Router();
-
-      for (final String mappingUrl : mappings.keys()) {
-        final JsonValue mapping = mappings.get(mappingUrl);
-        final CollectionResourceProvider provider = Rest2LDAP.builder()
-                .authorizationPolicy(AuthorizationPolicy.REUSE)
-                .configureMapping(mapping)
-                .build();
-        router.addRoute(Router.uriTemplate(mappingUrl), provider);
-      }
-      return router;
-    }
-
-    @Override
-    public final Promise<Response, NeverThrowsException> handle(final Context context, final Request request)
-    {
-      return delegate.handle(context, request);
-    }
-  }
-
-  private final URL configFileUrl;
-  private final boolean authenticationRequired;
-
-  Rest2LdapEmbeddedHttpApplication(URL configFileUrl, boolean authenticationRequired)
-  {
-    this.configFileUrl = configFileUrl;
-    this.authenticationRequired = authenticationRequired;
-  }
-
-  @Override
-  public Handler start() throws HttpApplicationException
-  {
-    try
-    {
-      final Object jsonElems = readJson(configFileUrl);
-      final JsonValue configuration = new JsonValue(jsonElems).recordKeyAccesses();
-      final Handler handler = Handlers.chainOf(
-          new Rest2LdapHandler(configuration),
-          new AuthenticationFilter(getAuthenticationConfig(configuration), authenticationRequired));
-      configuration.verifyAllKeysAccessed();
-      return handler;
-    }
-    catch (final Exception e)
-    {
-      stop();
-      throw new HttpApplicationException(ERR_INITIALIZE_HTTP_CONNECTION_HANDLER.get().toString(), e);
-    }
-  }
-
-  private static JsonValue readJson(final URL resource) throws IOException
-  {
-    try (final InputStream in = resource.openStream())
-    {
-      return new JsonValue(readJsonLenient(in));
-    }
-  }
-
-  private static HTTPAuthenticationConfig getAuthenticationConfig(final JsonValue configuration)
-  {
-    final HTTPAuthenticationConfig result = new HTTPAuthenticationConfig();
-
-    final JsonValue val = configuration.get("authenticationFilter");
-    result.setBasicAuthenticationSupported(asBool(val, "supportHTTPBasicAuthentication"));
-    result.setCustomHeadersAuthenticationSupported(asBool(val, "supportAltAuthentication"));
-    result.setCustomHeaderUsername(val.get("altAuthenticationUsernameHeader").asString());
-    result.setCustomHeaderPassword(val.get("altAuthenticationPasswordHeader").asString());
-
-    result.setSearchBaseDN(DN.valueOf(asString(val, "searchBaseDN")));
-    result.setSearchScope(SearchScope.valueOf(asString(val, "searchScope")));
-    result.setSearchFilterTemplate(asString(val, "searchFilterTemplate"));
-
-    return result;
-  }
-
-  private static String asString(JsonValue value, String key)
-  {
-    return value.get(key).required().asString();
-  }
-
-  private static boolean asBool(JsonValue value, String key)
-  {
-    return value.get(key).defaultTo(false).asBoolean();
-  }
-
-  @Override
-  public Factory<Buffer> getBufferFactory()
-  {
-    return null;
-  }
-
-  @Override
-  public void stop()
-  {
-    // Nothing to do
-  }
-}
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 e4f4da4..706347e 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
@@ -23,26 +23,39 @@
 import java.net.URISyntaxException;
 import java.net.URL;
 
+import org.forgerock.http.Filter;
 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.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.types.InitializationException;
 
 /**
- * Encapsulates configuration required to start a rest2ldap application in an
- * OpenDJ context. Acts as a factory for {@link Rest2LdapEmbeddedHttpApplication}.
+ * Encapsulates configuration required to start a REST2LDAP application embedded
+ * in this LDAP server. Acts as a factory for {@link Rest2LDAPHttpApplication}.
  */
 public final class Rest2LdapEndpoint extends HttpEndpoint<Rest2ldapEndpointCfg>
 {
+
   /**
    * Create a new Rest2LdapEnpoint with the supplied configuration.
    *
    * @param configuration
    *          Configuration to use for the {@link HttpApplication}
+   * @param serverContext
+   *          Server of this LDAP server
    */
-  public Rest2LdapEndpoint(Rest2ldapEndpointCfg configuration)
+  public Rest2LdapEndpoint(Rest2ldapEndpointCfg configuration, ServerContext serverContext)
   {
-    super(configuration);
+    super(configuration, serverContext);
   }
 
   @Override
@@ -53,7 +66,7 @@
       final URI configURI = new URI(configuration.getConfigUrl());
       final URL absoluteConfigUrl =
           configURI.isAbsolute() ? configURI.toURL() : getFileForPath(configuration.getConfigUrl()).toURI().toURL();
-      return new Rest2LdapEmbeddedHttpApplication(absoluteConfigUrl, configuration.isAuthenticationRequired());
+      return new InternalRest2LDAPHttpApplication(absoluteConfigUrl, serverContext.getSchemaNG());
     }
     catch (MalformedURLException | URISyntaxException e)
     {
@@ -62,4 +75,31 @@
     }
   }
 
+  /**
+   * Specialized {@link Rest2LDAPHttpApplication} using internal connections to
+   * this local LDAP server.
+   */
+  private final class InternalRest2LDAPHttpApplication extends Rest2LDAPHttpApplication
+  {
+    private final ConnectionFactory rootInternalConnectionFactory = Adapters.newRootConnectionFactory();
+
+    InternalRest2LDAPHttpApplication(URL configURL, Schema schema)
+    {
+      super(configURL, schema);
+    }
+
+    @Override
+    protected Filter newProxyAuthzFilter(final ConnectionFactory connectionFactory,
+        final Function<SecurityContext, String, LdapException> authzIdProvider)
+    {
+      return new InternalProxyAuthzFilter(DirectoryServer.getProxiedAuthorizationIdentityMapper(), schema,
+          authzIdProvider);
+    }
+
+    @Override
+    protected ConnectionFactory getConnectionFactory(String name)
+    {
+      return rootInternalConnectionFactory;
+    }
+  }
 }
diff --git a/opendj-server-legacy/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java b/opendj-server-legacy/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java
index d48cad3..5b39ec2 100644
--- a/opendj-server-legacy/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/forgerock/opendj/adapter/server3x/AdaptersTestCase.java
@@ -65,6 +65,8 @@
 import org.forgerock.util.Options;
 import org.opends.server.DirectoryServerTestCase;
 import org.opends.server.TestCaseUtils;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.types.DirectoryException;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.DataProvider;
@@ -76,18 +78,6 @@
 public class AdaptersTestCase extends DirectoryServerTestCase {
     private static final String USER_0_DN_STRING = "uid=user.0,o=test";
 
-    /**
-     * Provides an anonymous connection factories.
-     *
-     * @return Anonymous connection factories.
-     */
-    @DataProvider
-    public Object[][] anonymousConnectionFactories() {
-        return new Object[][] {
-            { new LDAPConnectionFactory("localhost", getServerLdapPort()) },
-            { Adapters.newAnonymousConnectionFactory() } };
-    }
-
     private Integer getServerLdapPort() {
         return TestCaseUtils.getServerLdapPort();
     }
@@ -96,9 +86,10 @@
      * Provides root connection factories.
      *
      * @return Root connection factories.
+     * @throws DirectoryException 
      */
     @DataProvider
-    public Object[][] rootConnectionFactories() {
+    public Object[][] rootConnectionFactories() throws DirectoryException {
         return new Object[][] {
             { new LDAPConnectionFactory("localhost",
                                         getServerLdapPort(),
@@ -106,7 +97,8 @@
                                                .set(AUTHN_BIND_REQUEST,
                                                     newSimpleBindRequest("cn=directory manager",
                                                                                   "password".toCharArray()))) },
-            { Adapters.newConnectionFactoryForUser(DN.valueOf("cn=directory manager")) } };
+            { Adapters.newConnectionFactory(new InternalClientConnection(DN.valueOf("cn=directory manager"))) },
+            { Adapters.newRootConnectionFactory() } };
     }
 
     /**
@@ -120,7 +112,7 @@
         TestCaseUtils.startServer();
 
         // Creates a root connection to add data
-        final Connection connection = Adapters.newRootConnection();
+        final Connection connection = Adapters.newRootConnectionFactory().getConnection();
         // @formatter:off
         connection.add(
                 "dn: uid=user.0, o=test",
@@ -211,8 +203,8 @@
      */
     @Test
     public void testSimpleLDAPConnectionFactorySimpleBind() throws LdapException {
-        final LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", getServerLdapPort());
-        try (Connection connection = factory.getConnection()) {
+        try (final LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", getServerLdapPort());
+             final Connection connection = factory.getConnection()) {
             connection.bind("cn=Directory Manager", "password".toCharArray());
             assertThat(connection.isValid()).isTrue();
             assertThat(connection.isClosed()).isFalse();
@@ -228,12 +220,9 @@
      */
     @Test
     public void testLDAPSASLBind() throws NumberFormatException, GeneralSecurityException, LdapException {
-        LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", getServerLdapPort());
-
-        PlainSASLBindRequest request =
-                Requests.newPlainSASLBindRequest("u:user.0", "password".toCharArray());
-
-        try (Connection connection = factory.getConnection()) {
+        final PlainSASLBindRequest request = Requests.newPlainSASLBindRequest("u:user.0", "password".toCharArray());
+        try (final LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", getServerLdapPort());
+             final Connection connection = factory.getConnection()) {
             connection.bind(request);
         }
     }
@@ -244,36 +233,22 @@
      * @throws LdapException
      */
     @Test
-    public void testAdapterConnectionSASLBindRequest() throws LdapException,
-            GeneralSecurityException {
+    public void testAdapterConnectionSASLBindRequest() throws LdapException {
         PlainSASLBindRequest request =
                 Requests.newPlainSASLBindRequest("u:user.0", "password".toCharArray());
-        try (final Connection connection = Adapters.newRootConnection()) {
+        try (final Connection connection = Adapters.newRootConnectionFactory().getConnection()) {
             connection.bind(request);
         }
     }
 
     /**
-     * This type of connection is not supported. Anonymous SASL Mechanisms is
-     * disabled in the config.ldif file.
-     *
-     * @throws LdapException
-     */
-    @Test(dataProvider = "anonymousConnectionFactories", expectedExceptions = LdapException.class)
-    public void testConnectionAnonymousSASLBindRequest(final ConnectionFactory factory) throws LdapException {
-        try (final Connection connection = factory.getConnection()) {
-            connection.bind(Requests.newAnonymousSASLBindRequest("anonymousSASLBindRequest"));
-        }
-    }
-
-    /**
      * Binds as a root.
      *
      * @throws Exception
      */
     @Test
     public void testAdapterConnectionSimpleBindAsRoot() throws Exception {
-        try (final Connection connection = Adapters.newRootConnection()) {
+        try (final Connection connection = Adapters.newRootConnectionFactory().getConnection()) {
             final BindResult result = connection.bind("cn=Directory Manager", "password".toCharArray());
             assertThat(connection.isValid()).isTrue();
             assertThat(result.getResultCode()).isEqualTo(ResultCode.SUCCESS);
@@ -287,7 +262,8 @@
      */
     @Test
     public void testAdapterConnectionSimpleBindAsAUser() throws Exception {
-        try (final Connection connection = Adapters.newConnectionForUser(DN.valueOf(USER_0_DN_STRING))) {
+        try (final Connection connection = Adapters.newConnectionFactory(
+                new InternalClientConnection(DN.valueOf(USER_0_DN_STRING))).getConnection()) {
             final BindResult result = connection.bind(USER_0_DN_STRING, "password".toCharArray());
             assertThat(result.getResultCode()).isEqualTo(ResultCode.SUCCESS);
         }
@@ -300,34 +276,21 @@
      */
     @Test(expectedExceptions = AuthenticationException.class)
     public void testAdapterConnectionSimpleBindAsAUserWrongPassword() throws Exception {
-        try (final Connection connection = Adapters.newConnectionForUser(DN.valueOf(USER_0_DN_STRING))) {
+        try (final Connection connection =  Adapters.newConnectionFactory(
+                new InternalClientConnection(DN.valueOf(USER_0_DN_STRING))).getConnection()) {
             // Invalid credentials
             connection.bind(USER_0_DN_STRING, "pass".toCharArray());
         }
     }
 
     /**
-     * Tries to bind as anonymous.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testAdapterConnectionSimpleBind() throws Exception {
-        // Anonymous
-        try (final Connection connection = Adapters.newAnonymousConnection()) {
-            final BindResult result = connection.bind("", "".toCharArray());
-            assertThat(result.getDiagnosticMessage()).isEmpty();
-        }
-    }
-
-    /**
      * Testing the adapters with a simple add request.
      *
      * @throws Exception
      */
     @Test
     public void testAdapterAddRequest() throws Exception {
-        final Connection connection = Adapters.newRootConnection();
+        final Connection connection = Adapters.newRootConnectionFactory().getConnection();
         // @formatter:off
         final AddRequest addRequest = Requests.newAddRequest(
                 "dn: sn=carter,o=test",
@@ -383,7 +346,7 @@
      */
     @Test
     public void testAdapterSearchRequest() throws Exception {
-        final Connection connection = Adapters.newRootConnection();
+        final Connection connection = Adapters.newRootConnectionFactory().getConnection();
 
         final SearchRequest request =
                 Requests.newSearchRequest("o=test", SearchScope.WHOLE_SUBTREE,
@@ -564,7 +527,7 @@
      */
     @Test
     public void testAdapterDeleteRequest() throws LdapException {
-        try (final Connection connection = Adapters.newRootConnection()) {
+        try (final Connection connection = Adapters.newRootConnectionFactory().getConnection()) {
             // Checks if the entry exists.
             SearchResultEntry sre =
                     connection.searchSingleEntry(Requests.newSearchRequest(
@@ -598,7 +561,7 @@
                         PreReadRequestControl.newControl(true, "mail")).addModification(
                         ModificationType.ADD, "mail", "modified@example.com");
 
-        final Connection connection = Adapters.newRootConnection();
+        final Connection connection = Adapters.newRootConnectionFactory().getConnection();
         final Result result = connection.modify(changeRequest);
         assertThat(result.getDiagnosticMessage()).isEmpty();
         assertThat(result.getControls()).isNotEmpty();
@@ -746,150 +709,6 @@
     }
 
     /**
-     * If an anonymous tries to delete, sends a result code : insufficient
-     * access rights.
-     *
-     * @throws LdapException
-     */
-    @Test(dataProvider = "anonymousConnectionFactories",
-            expectedExceptions = AuthorizationException.class)
-    public void testAdapterAsAnonymousCannotPerformDeleteRequest(final ConnectionFactory factory)
-            throws LdapException {
-        final DeleteRequest deleteRequest =
-                Requests.newDeleteRequest("uid=user.2,o=test");
-
-        try (final Connection connection = factory.getConnection()) {
-            connection.delete(deleteRequest);
-        }
-    }
-
-    /**
-     * If an anonymous tries to do an add request, sends a result code :
-     * insufficient access rights.
-     *
-     * @throws LdapException
-     */
-    @Test(dataProvider = "anonymousConnectionFactories",
-            expectedExceptions = AuthorizationException.class)
-    public void testAdapterAsAnonymousCannotPerformAddRequest(final ConnectionFactory factory)
-            throws LdapException {
-        // @formatter:off
-        final AddRequest addRequest = Requests.newAddRequest(
-                "dn: sn=scarter,o=test",
-                "objectClass: top",
-                "objectClass: person",
-                "cn: scarter");
-        // @formatter:on
-
-        try (final Connection connection = factory.getConnection()) {
-            connection.add(addRequest);
-        }
-    }
-
-    /**
-     * If an anonymous tries to do a modify DN request, sends a result code :
-     * insufficient access rights.
-     *
-     * @throws LdapException
-     */
-    @Test(dataProvider = "anonymousConnectionFactories",
-            expectedExceptions = AuthorizationException.class)
-    public void testAdapterAsAnonymousCannotPerformModifyDNRequest(final ConnectionFactory factory)
-            throws LdapException {
-        final ModifyDNRequest changeRequest =
-                Requests.newModifyDNRequest("uid=user.2,o=test", "uid=user.test")
-                        .setDeleteOldRDN(true);
-        try (final Connection connection = factory.getConnection()) {
-            connection.modifyDN(changeRequest);
-        }
-    }
-
-    /**
-     * If an anonymous tries to do a modify request, sends a result code :
-     * insufficient access rights.
-     *
-     * @throws LdapException
-     */
-    @Test(dataProvider = "anonymousConnectionFactories",
-            expectedExceptions = LdapException.class)
-    public void testAdapterAsAnonymousCannotPerformModifyRequest(final ConnectionFactory factory)
-            throws LdapException {
-        final ModifyRequest changeRequest =
-                Requests.newModifyRequest("uid=user.2,o=test").addControl(
-                        PreReadRequestControl.newControl(true, "mail")).addModification(
-                        ModificationType.REPLACE, "mail", "modified@example.com");
-
-        try (final Connection connection = factory.getConnection()) {
-            connection.modify(changeRequest);
-        }
-    }
-
-    /**
-     * The anonymous connection is allowed to perform compare request.
-     *
-     * @throws LdapException
-     */
-    @Test(dataProvider = "anonymousConnectionFactories")
-    public void testAdapterAsAnonymousPerformsCompareRequest(final ConnectionFactory factory)
-            throws LdapException {
-        final CompareRequest compareRequest =
-                Requests.newCompareRequest(USER_0_DN_STRING, "uid", "user.0");
-
-        try (final Connection connection = factory.getConnection()) {
-            final CompareResult result = connection.compare(compareRequest);
-            assertThat(result.getResultCode()).isEqualTo(ResultCode.COMPARE_TRUE);
-
-            assertThat(result.getDiagnosticMessage()).isEmpty();
-            assertThat(result.getControls()).isEmpty();
-            assertThat(result.getMatchedDN()).isEmpty();
-        }
-    }
-
-    /**
-     * The anonymous connection is allowed to perform search request.
-     *
-     * @throws Exception
-     */
-    @Test(dataProvider = "anonymousConnectionFactories")
-    public void testAdapterAsAnonymousPerformsSearchRequest(final ConnectionFactory factory)
-            throws Exception {
-        final SearchRequest request =
-                Requests.newSearchRequest("o=test", SearchScope.WHOLE_SUBTREE,
-                        "(uid=user.1)");
-
-        final Connection connection = factory.getConnection();
-        final ConnectionEntryReader reader = connection.search(request);
-
-        assertThat(reader.isEntry()).isTrue();
-        final SearchResultEntry entry = reader.readEntry();
-        assertThat(entry).isNotNull();
-        assertThat(entry.getName().toString()).isEqualTo("uid=user.1,o=test");
-        assertThat(reader.hasNext()).isFalse();
-    }
-
-    /**
-     * The anonymous connection is not allowed to perform search request
-     * associated with a control.
-     * <p>
-     * Unavailable Critical Extension: The request control with Object
-     * Identifier (OID) "x.x.x" cannot be used due to insufficient access
-     * rights.
-     *
-     * @throws Exception
-     */
-    @Test(dataProvider = "anonymousConnectionFactories", expectedExceptions = LdapException.class)
-    public void testAdapterAsAnonymousCannotPerformSearchRequestWithControl(
-            final ConnectionFactory factory) throws Exception {
-        final Connection connection = factory.getConnection();
-        final SearchRequest request =
-                Requests.newSearchRequest("o=test", SearchScope.WHOLE_SUBTREE,
-                        "(uid=user.1)").addControl(ADNotificationRequestControl.newControl(true));
-
-        final ConnectionEntryReader reader = connection.search(request);
-        reader.readEntry();
-    }
-
-    /**
      * Creates an LDAP Connection and performs some basic calls like
      * add/delete/search and compare results with an SDK adapter connection
      * doing the same.
@@ -931,7 +750,7 @@
         assertThat(deleteResult.getResultCode()).isEqualTo(ResultCode.SUCCESS);
 
         // SDK Adapter connection
-        final Connection adapterConnection = Adapters.newRootConnection();
+        final Connection adapterConnection = Adapters.newRootConnectionFactory().getConnection();
         final Result sdkAddResult = adapterConnection.add(addRequest);
         final ConnectionEntryReader sdkReader = adapterConnection.search(searchRequest);
         final Result sdkDeleteResult = adapterConnection.delete(deleteRequest);
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/protocols/http/AuthenticationFilterTest.java b/opendj-server-legacy/src/test/java/org/opends/server/protocols/http/AuthenticationFilterTest.java
deleted file mode 100644
index 9a13224..0000000
--- a/opendj-server-legacy/src/test/java/org/opends/server/protocols/http/AuthenticationFilterTest.java
+++ /dev/null
@@ -1,135 +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 2013-2016 ForgeRock AS.
- */
-package org.opends.server.protocols.http;
-
-import static org.opends.server.protocols.http.AuthenticationFilter.*;
-import static org.assertj.core.api.Assertions.*;
-
-import java.io.IOException;
-
-import org.assertj.core.api.SoftAssertions;
-import org.forgerock.http.protocol.Request;
-import org.forgerock.http.protocol.Response;
-import org.forgerock.json.resource.ResourceException;
-import org.opends.server.DirectoryServerTestCase;
-import org.opends.server.util.Base64;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-@SuppressWarnings("javadoc")
-public class AuthenticationFilterTest extends DirectoryServerTestCase
-{
-  private static final String USERNAME = "Aladdin";
-  private static final String PASSWORD = "open sesame";
-  private static final String BASE64_USERPASS = Base64.encode((USERNAME + ":" + PASSWORD).getBytes());
-
-  private HTTPAuthenticationConfig authConfig;
-  private AuthenticationFilter filter;
-
-  @BeforeMethod
-  private void createConfigAndFilter()
-  {
-    authConfig = new HTTPAuthenticationConfig();
-    filter = new AuthenticationFilter(authConfig, false);
-  }
-
-  @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
-  {
-    assertThat(filter.parseUsernamePassword(authZHeader)).isNull();
-  }
-
-  @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
-  {
-    assertThat(filter.parseUsernamePassword(authZHeader)).containsExactly(USERNAME, PASSWORD);
-  }
-
-  @Test
-  public void sendUnauthorizedResponseWithHttpBasicAuthWillChallengeUserAgent() throws Exception
-  {
-    authConfig.setBasicAuthenticationSupported(true);
-    final Response response = sendUnauthorizedResponseWithHTTPBasicAuthChallenge();
-
-    assertThat(response.getHeaders().getFirst("WWW-Authenticate")).isEqualTo("Basic realm=\"org.forgerock.opendj\"");
-    verifyUnauthorizedOutputMessage(response);
-  }
-
-  @Test
-  public void sendUnauthorizedResponseWithoutHttpBasicAuthWillNotChallengeUserAgent() throws Exception
-  {
-    authConfig.setBasicAuthenticationSupported(false);
-    final Response response = sendUnauthorizedResponseWithHTTPBasicAuthChallenge();
-
-    assertThat(response.getHeaders().getFirst("WWW-Authenticate")).isNull();
-    verifyUnauthorizedOutputMessage(response);
-  }
-
-  private Response sendUnauthorizedResponseWithHTTPBasicAuthChallenge() throws Exception
-  {
-    return filter.resourceExceptionToPromise(ResourceException.getException(401, "Invalid Credentials")).get();
-  }
-
-  private void verifyUnauthorizedOutputMessage(Response response) throws IOException
-  {
-    final SoftAssertions softly = new SoftAssertions();
-    softly.assertThat(response.getStatus().getCode()).isEqualTo(401);
-    softly.assertThat(response.getStatus().getReasonPhrase()).isEqualTo("Unauthorized");
-    softly.assertThat(response.getEntity().getJson().toString()).isEqualTo(
-            "{code=401, reason=Unauthorized, message=Invalid Credentials}");
-    softly.assertAll();
-  }
-
-  @Test
-  public void extractUsernamePasswordHttpBasicAuthWillAcceptUserAgent() throws Exception
-  {
-    authConfig.setBasicAuthenticationSupported(true);
-
-    final Request request = new Request();
-    request.getHeaders().add(HTTP_BASIC_AUTH_HEADER, "Basic " + BASE64_USERPASS);
-    assertThat(filter.extractUsernamePassword(request)).containsExactly(USERNAME, PASSWORD);
-  }
-
-  @Test
-  public void extractUsernamePasswordCustomHeaders() throws Exception
-  {
-    final String customHeaderUsername = "X-OpenIDM-Username";
-    final String customHeaderPassword = "X-OpenIDM-Password";
-
-    authConfig.setCustomHeadersAuthenticationSupported(true);
-    authConfig.setCustomHeaderUsername(customHeaderUsername);
-    authConfig.setCustomHeaderPassword(customHeaderPassword);
-
-    final Request request = new Request();
-    request.getHeaders().add(customHeaderUsername, USERNAME);
-    request.getHeaders().add(customHeaderPassword, PASSWORD);
-
-    assertThat(filter.extractUsernamePassword(request)).containsExactly(USERNAME, PASSWORD);
-  }
-}

--
Gitblit v1.10.0