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

---
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java |  143 ++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 121 insertions(+), 22 deletions(-)

diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
index 0a65a76..4136956 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
@@ -16,6 +16,7 @@
 package org.forgerock.opendj.rest2ldap;
 
 import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
+import static org.forgerock.opendj.rest2ldap.Utils.adapt;
 
 import java.io.Closeable;
 import java.util.LinkedHashMap;
@@ -24,6 +25,9 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.forgerock.json.resource.InternalServerErrorException;
+import org.forgerock.json.resource.ResourceException;
+import org.forgerock.json.resource.SecurityContext;
 import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
 import org.forgerock.opendj.ldap.Connection;
@@ -35,6 +39,8 @@
 import org.forgerock.opendj.ldap.ResultHandler;
 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;
@@ -43,6 +49,7 @@
 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;
@@ -190,14 +197,21 @@
     };
 
     private final Config config;
-
     private final AtomicReference<Connection> connection = new AtomicReference<Connection>();
-
     private final ServerContext context;
+    private final Connection preAuthenticatedConnection;
+    private Control proxiedAuthzControl = null;
 
     Context(final Config config, final ServerContext context) {
         this.config = config;
         this.context = context;
+        if (context.containsContext(AuthenticatedConnectionContext.class)) {
+            final Connection connection =
+                    context.asContext(AuthenticatedConnectionContext.class).getConnection();
+            this.preAuthenticatedConnection = connection != null ? wrap(connection) : null;
+        } else {
+            this.preAuthenticatedConnection = null;
+        }
     }
 
     /**
@@ -205,6 +219,11 @@
      */
     @Override
     public void close() {
+        /*
+         * Only release the connection that we acquired. Don't release the
+         * cached connection since that is the responsibility of the component
+         * which acquired it.
+         */
         final Connection c = connection.getAndSet(null);
         if (c != null) {
             c.close();
@@ -216,28 +235,98 @@
     }
 
     Connection getConnection() {
-        return connection.get();
+        return preAuthenticatedConnection != null ? preAuthenticatedConnection : connection.get();
     }
 
     ServerContext getServerContext() {
         return context;
     }
 
-    void setConnection(final Connection connection) {
-        if (!this.connection.compareAndSet(null, withCache(connection))) {
-            throw new IllegalStateException("LDAP connection obtained multiple times");
+    /**
+     * Performs common processing required before handling an HTTP request,
+     * including calculating the proxied authorization request control, and
+     * obtaining an LDAP connection.
+     * <p>
+     * This method should be called at most once per request.
+     *
+     * @param handler
+     *            The result handler which should be invoked if an error is
+     *            detected.
+     * @param runnable
+     *            The runnable which will be invoked once the common processing
+     *            has completed. Implementations will be able to call
+     *            {@link #getConnection()} to get the LDAP connection for use
+     *            with subsequent LDAP requests.
+     */
+    void run(final org.forgerock.json.resource.ResultHandler<?> handler, final Runnable runnable) {
+        /*
+         * 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 (preAuthenticatedConnection == null && config.useProxiedAuthorization()) {
+            if (context.containsContext(SecurityContext.class)) {
+                try {
+                    final SecurityContext securityContext =
+                            context.asContext(SecurityContext.class);
+                    final String authzId =
+                            config.getProxiedAuthorizationTemplate().formatAsAuthzId(
+                                    securityContext.getAuthorizationId(), config.schema());
+                    proxiedAuthzControl = ProxiedAuthV2RequestControl.newControl(authzId);
+                } catch (final ResourceException e) {
+                    handler.handleError(e);
+                    return;
+                }
+            } else {
+                // FIXME: i18n.
+                handler.handleError(new InternalServerErrorException(
+                        "The request could not be authorized because it did not contain a security context"));
+                return;
+            }
+        }
+
+        /*
+         * 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 (preAuthenticatedConnection != null) {
+            // Invoke the handler immediately since a connection is available.
+            runnable.run();
+        } else if (config.connectionFactory() != null) {
+            config.connectionFactory().getConnectionAsync(new ResultHandler<Connection>() {
+                @Override
+                public final void handleErrorResult(final ErrorResultException error) {
+                    handler.handleError(adapt(error));
+                }
+
+                @Override
+                public final void handleResult(final Connection result) {
+                    if (!connection.compareAndSet(null, wrap(result))) {
+                        // This should never happen.
+                        throw new IllegalStateException("LDAP connection obtained multiple times");
+                    }
+                    runnable.run();
+                }
+            });
+        } else {
+            // FIXME: i18n
+            handler.handleError(new InternalServerErrorException(
+                    "The request could not be processed because there was no LDAP connection available for use"));
         }
     }
 
     /*
-     * Adds read caching support to the provided connection.
+     * Adds read caching support to the provided connection as well
+     * functionality which automatically adds the proxied authorization control
+     * if needed.
      */
-    private Connection withCache(final Connection connection) {
+    private Connection wrap(final Connection connection) {
         /*
          * We only use async methods so no need to wrap sync methods.
          */
         return new AbstractAsynchronousConnection() {
-
             @Override
             public FutureResult<Void> abandonAsync(final AbandonRequest request) {
                 return connection.abandonAsync(request);
@@ -247,7 +336,8 @@
             public FutureResult<Result> addAsync(final AddRequest request,
                     final IntermediateResponseHandler intermediateResponseHandler,
                     final ResultHandler<? super Result> resultHandler) {
-                return connection.addAsync(request, intermediateResponseHandler, resultHandler);
+                return connection.addAsync(withControls(request), intermediateResponseHandler,
+                        resultHandler);
             }
 
             @Override
@@ -276,7 +366,8 @@
             public FutureResult<CompareResult> compareAsync(final CompareRequest request,
                     final IntermediateResponseHandler intermediateResponseHandler,
                     final ResultHandler<? super CompareResult> resultHandler) {
-                return connection.compareAsync(request, intermediateResponseHandler, resultHandler);
+                return connection.compareAsync(withControls(request), intermediateResponseHandler,
+                        resultHandler);
             }
 
             @Override
@@ -284,7 +375,8 @@
                     final IntermediateResponseHandler intermediateResponseHandler,
                     final ResultHandler<? super Result> resultHandler) {
                 evict(request.getName());
-                return connection.deleteAsync(request, intermediateResponseHandler, resultHandler);
+                return connection.deleteAsync(withControls(request), intermediateResponseHandler,
+                        resultHandler);
             }
 
             @Override
@@ -297,8 +389,8 @@
                  * operation modifies an entry: clear the cachedReads.
                  */
                 evictAll();
-                return connection.extendedRequestAsync(request, intermediateResponseHandler,
-                        resultHandler);
+                return connection.extendedRequestAsync(withControls(request),
+                        intermediateResponseHandler, resultHandler);
             }
 
             @Override
@@ -316,7 +408,8 @@
                     final IntermediateResponseHandler intermediateResponseHandler,
                     final ResultHandler<? super Result> resultHandler) {
                 evict(request.getName());
-                return connection.modifyAsync(request, intermediateResponseHandler, resultHandler);
+                return connection.modifyAsync(withControls(request), intermediateResponseHandler,
+                        resultHandler);
             }
 
             @Override
@@ -325,8 +418,8 @@
                     final ResultHandler<? super Result> resultHandler) {
                 // Simple brute force implementation: clear the cachedReads.
                 evictAll();
-                return connection
-                        .modifyDNAsync(request, intermediateResponseHandler, resultHandler);
+                return connection.modifyDNAsync(withControls(request), intermediateResponseHandler,
+                        resultHandler);
             }
 
             @Override
@@ -341,7 +434,6 @@
             public FutureResult<Result> searchAsync(final SearchRequest request,
                     final IntermediateResponseHandler intermediateResponseHandler,
                     final SearchResultHandler resultHandler) {
-
                 /*
                  * Don't attempt caching if this search is not a read (base
                  * object), or if the search request passed in an intermediate
@@ -349,8 +441,8 @@
                  */
                 if (!request.getScope().equals(SearchScope.BASE_OBJECT)
                         || intermediateResponseHandler != null) {
-                    return connection.searchAsync(request, intermediateResponseHandler,
-                            resultHandler);
+                    return connection.searchAsync(withControls(request),
+                            intermediateResponseHandler, resultHandler);
                 }
 
                 // This is a read request and a candidate for caching.
@@ -369,8 +461,8 @@
                         cachedReads.put(request.getName(), pendingCachedRead);
                     }
                     final FutureResult<Result> future =
-                            connection.searchAsync(request, intermediateResponseHandler,
-                                    pendingCachedRead);
+                            connection.searchAsync(withControls(request),
+                                    intermediateResponseHandler, pendingCachedRead);
                     pendingCachedRead.setFuture(future);
                     return future;
                 }
@@ -392,6 +484,13 @@
                     cachedReads.clear();
                 }
             }
+
+            private <R extends Request> R withControls(final R request) {
+                if (proxiedAuthzControl != null) {
+                    request.addControl(proxiedAuthzControl);
+                }
+                return request;
+            }
         };
     }
 

--
Gitblit v1.10.0