From b57eed310c4425bf23be809c49b4d4699ea90500 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Mon, 25 Mar 2013 11:05:34 +0000
Subject: [PATCH] Partial fix for OPENDJ-694: Implement HTTP BASIC authentication

---
 opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java |  185 ++++++++++++++++++++++++++++++++++++----------
 1 files changed, 145 insertions(+), 40 deletions(-)

diff --git a/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java b/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java
index 6b3d787..db102bf 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java
@@ -20,11 +20,16 @@
 import static org.forgerock.json.resource.SecurityContext.AUTHZID_ID;
 import static org.forgerock.json.resource.servlet.SecurityContextFactory.ATTRIBUTE_AUTHCID;
 import static org.forgerock.json.resource.servlet.SecurityContextFactory.ATTRIBUTE_AUTHZID;
+import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
+import static org.forgerock.opendj.ldap.requests.Requests.newPlainSASLBindRequest;
+import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
+import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
 import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
 import static org.forgerock.opendj.rest2ldap.servlet.Rest2LDAPContextFactory.ATTRIBUTE_AUTHN_CONNECTION;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.StringTokenizer;
@@ -46,15 +51,22 @@
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.json.resource.servlet.CompletionHandler;
 import org.forgerock.json.resource.servlet.CompletionHandlerFactory;
+import org.forgerock.opendj.ldap.AuthenticationException;
+import org.forgerock.opendj.ldap.AuthorizationException;
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.EntryNotFoundException;
 import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
+import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.ResultHandler;
 import org.forgerock.opendj.ldap.SearchScope;
-import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.ldap.requests.BindRequest;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
 import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
 import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.opendj.rest2ldap.Rest2LDAP;
 
@@ -74,12 +86,12 @@
     private static final ObjectMapper JSON_MAPPER = new ObjectMapper().configure(
             JsonParser.Feature.ALLOW_COMMENTS, true);
 
-    /** Indicates whether or not authentication should be performed. */
-    private boolean isEnabled = false;
     private String altAuthenticationPasswordHeader;
     private String altAuthenticationUsernameHeader;
     private AuthenticationMethod authenticationMethod = AuthenticationMethod.SEARCH_SIMPLE;
     private ConnectionFactory bindLDAPConnectionFactory;
+    /** Indicates whether or not authentication should be performed. */
+    private boolean isEnabled = false;
     private boolean reuseAuthenticatedConnection = true;
     private String saslAuthzIdTemplate;
     private final Schema schema = Schema.getDefaultSchema();
@@ -179,55 +191,100 @@
 
             // If we've got here then we have a username and password.
             switch (authenticationMethod) {
-            case SIMPLE:
-                bindLDAPConnectionFactory.getConnectionAsync(new ResultHandler<Connection>() {
+            case SIMPLE: {
+                final Map<String, Object> authzid;
+                authzid = new LinkedHashMap<String, Object>(2);
+                authzid.put(AUTHZID_DN, username);
+                authzid.put(AUTHZID_ID, username);
+                doBind(req, response, newSimpleBindRequest(username, password), chain,
+                        savedConnection, completionHandler, username, authzid);
+                break;
+            }
+            case SASL_PLAIN: {
+                final Map<String, Object> authzid;
+                final String bindId;
+                if (saslAuthzIdTemplate.startsWith("dn:")) {
+                    final String bindDN =
+                            DN.format(saslAuthzIdTemplate.substring(3), schema, username)
+                                    .toString();
+                    bindId = "dn:" + bindDN;
+                    authzid = new LinkedHashMap<String, Object>(2);
+                    authzid.put(AUTHZID_DN, bindDN);
+                    authzid.put(AUTHZID_ID, username);
+                } else {
+                    bindId = String.format(saslAuthzIdTemplate, username);
+                    authzid = Collections.singletonMap(AUTHZID_ID, (Object) username);
+                }
+                doBind(req, response, newPlainSASLBindRequest(bindId, password), chain,
+                        savedConnection, completionHandler, username, authzid);
+                break;
+            }
+            default: // SEARCH_SIMPLE
+            {
+                /*
+                 * First do a search to find the user's entry and then perform a
+                 * bind request using the user's DN.
+                 */
+                final org.forgerock.opendj.ldap.Filter filter =
+                        org.forgerock.opendj.ldap.Filter.format(searchFilterTemplate, username);
+                final SearchRequest searchRequest =
+                        newSearchRequest(searchBaseDN, searchScope, filter, "1.1");
+                searchLDAPConnectionFactory.getConnectionAsync(new ResultHandler<Connection>() {
                     @Override
-                    public void handleErrorResult(ErrorResultException error) {
+                    public void handleErrorResult(final ErrorResultException error) {
                         completionHandler.onError(asResourceException(error));
                     }
 
                     @Override
                     public void handleResult(final Connection connection) {
-                        savedConnection.set(connection);
-                        connection.bindAsync(Requests.newSimpleBindRequest(username, password),
-                                null, new ResultHandler<BindResult>() {
+                        // Do the search.
+                        connection.searchSingleEntryAsync(searchRequest,
+                                new ResultHandler<SearchResultEntry>() {
 
                                     @Override
-                                    public void handleErrorResult(ErrorResultException error) {
-                                        completionHandler.onError(asResourceException(error));
+                                    public void handleErrorResult(final ErrorResultException error) {
+                                        connection.close();
+                                        /*
+                                         * The search error should not be passed
+                                         * as-is back to the user.
+                                         */
+                                        final ErrorResultException normalizedError;
+                                        if (error instanceof EntryNotFoundException
+                                                || error instanceof MultipleEntriesFoundException) {
+                                            normalizedError =
+                                                    newErrorResult(ResultCode.INVALID_CREDENTIALS,
+                                                            error);
+                                        } else if (error instanceof AuthenticationException
+                                                || error instanceof AuthorizationException) {
+                                            normalizedError =
+                                                    newErrorResult(
+                                                            ResultCode.CLIENT_SIDE_LOCAL_ERROR,
+                                                            error);
+                                        } else {
+                                            normalizedError = error;
+                                        }
+                                        completionHandler
+                                                .onError(asResourceException(normalizedError));
                                     }
 
                                     @Override
-                                    public void handleResult(BindResult result) {
-                                        // Cache the pre-authenticated connection.
-                                        if (reuseAuthenticatedConnection) {
-                                            req.setAttribute(ATTRIBUTE_AUTHN_CONNECTION, connection);
-                                        }
-
-                                        // Pass through the authentication ID.
-                                        req.setAttribute(ATTRIBUTE_AUTHCID, username);
-
-                                        // Pass through authorization information.
+                                    public void handleResult(final SearchResultEntry result) {
+                                        connection.close();
+                                        final String bindDN = result.getName().toString();
                                         final Map<String, Object> authzid =
                                                 new LinkedHashMap<String, Object>(2);
-                                        authzid.put(AUTHZID_DN, username);
+                                        authzid.put(AUTHZID_DN, bindDN);
                                         authzid.put(AUTHZID_ID, username);
-                                        req.setAttribute(ATTRIBUTE_AUTHZID, authzid);
-
-                                        // Invoke the remained of the filter chain.
-                                        try {
-                                            chain.doFilter(request, response);
-                                        } catch (Throwable t) {
-                                            completionHandler.onError(asResourceException(t));
-                                        }
+                                        doBind(req, response,
+                                                newSimpleBindRequest(bindDN, password), chain,
+                                                savedConnection, completionHandler, username,
+                                                authzid);
                                     }
                                 });
                     }
                 });
                 break;
-            case SASL_PLAIN:
-            case SEARCH_SIMPLE:
-                throw ResourceException.getException(401);
+            }
             }
 
             /*
@@ -348,6 +405,61 @@
         }
     }
 
+    private void closeConnection(final AtomicReference<Connection> savedConnection) {
+        final Connection connection = savedConnection.get();
+        if (connection != null) {
+            connection.close();
+        }
+    }
+
+    /*
+     * Get a bind connection and then perform the bind operation, setting the
+     * cached connection and authorization credentials on completion.
+     */
+    private void doBind(final HttpServletRequest request, final ServletResponse response,
+            final BindRequest bindRequest, final FilterChain chain,
+            final AtomicReference<Connection> savedConnection,
+            final CompletionHandler completionHandler, final String authcid,
+            final Map<String, Object> authzid) {
+        bindLDAPConnectionFactory.getConnectionAsync(new ResultHandler<Connection>() {
+            @Override
+            public void handleErrorResult(final ErrorResultException error) {
+                completionHandler.onError(asResourceException(error));
+            }
+
+            @Override
+            public void handleResult(final Connection connection) {
+                savedConnection.set(connection);
+                connection.bindAsync(bindRequest, null, new ResultHandler<BindResult>() {
+
+                    @Override
+                    public void handleErrorResult(final ErrorResultException error) {
+                        completionHandler.onError(asResourceException(error));
+                    }
+
+                    @Override
+                    public void handleResult(final BindResult result) {
+                        // Cache the pre-authenticated connection.
+                        if (reuseAuthenticatedConnection) {
+                            request.setAttribute(ATTRIBUTE_AUTHN_CONNECTION, connection);
+                        }
+
+                        // Pass through the authentication ID and authorization principals.
+                        request.setAttribute(ATTRIBUTE_AUTHCID, authcid);
+                        request.setAttribute(ATTRIBUTE_AUTHZID, authzid);
+
+                        // Invoke the remained of the filter chain.
+                        try {
+                            chain.doFilter(request, response);
+                        } catch (final Throwable t) {
+                            completionHandler.onError(asResourceException(t));
+                        }
+                    }
+                });
+            }
+        });
+    }
+
     private AuthenticationMethod parseAuthenticationMethod(final JsonValue configuration) {
         if (configuration.isDefined("method")) {
             final String method = configuration.get("method").asString();
@@ -383,11 +495,4 @@
         }
     }
 
-    private void closeConnection(final AtomicReference<Connection> savedConnection) {
-        final Connection connection = savedConnection.get();
-        if (connection != null) {
-            connection.close();
-        }
-    }
-
 }

--
Gitblit v1.10.0