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

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