From 19536840ae2c706f4ad9ff4742e52dfc5f6ebea7 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Sat, 23 Mar 2013 01:46:37 +0000
Subject: [PATCH] Partial fix for OPENDJ-694: Implement HTTP BASIC authentication
---
opendj3/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/web.xml | 22 +
opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json | 8
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java | 4
opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPContextFactory.java | 4
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java | 7
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java | 205 +++++----------
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java | 57 ----
opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java | 393 ++++++++++++++++++++++++++++++
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java | 8
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java | 12
opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPConnectionFactoryProvider.java | 2
11 files changed, 512 insertions(+), 210 deletions(-)
diff --git a/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java b/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java
new file mode 100644
index 0000000..6b3d787
--- /dev/null
+++ b/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java
@@ -0,0 +1,393 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2013 ForgeRock AS.
+ */
+
+package org.forgerock.opendj.rest2ldap.servlet;
+
+import static org.forgerock.json.resource.SecurityContext.AUTHZID_DN;
+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.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.LinkedHashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.fluent.JsonValueException;
+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.ByteString;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.ResultHandler;
+import org.forgerock.opendj.ldap.SearchScope;
+import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.ldap.responses.BindResult;
+import org.forgerock.opendj.ldap.schema.Schema;
+import org.forgerock.opendj.rest2ldap.Rest2LDAP;
+
+/**
+ * An LDAP based authentication Servlet filter.
+ * <p>
+ * TODO: this is a work in progress. In particular, in order to embed this into
+ * the OpenDJ HTTP listener it will need to provide a configuration API.
+ */
+public final class Rest2LDAPAuthnFilter implements Filter {
+ /** Indicates how authentication should be performed. */
+ private static enum AuthenticationMethod {
+ SASL_PLAIN, SEARCH_SIMPLE, SIMPLE;
+ }
+
+ private static final String INIT_PARAM_CONFIG_FILE = "config-file";
+ 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;
+ private boolean reuseAuthenticatedConnection = true;
+ private String saslAuthzIdTemplate;
+ private final Schema schema = Schema.getDefaultSchema();
+ private DN searchBaseDN;
+ private String searchFilterTemplate;
+ private ConnectionFactory searchLDAPConnectionFactory;
+ private SearchScope searchScope = SearchScope.WHOLE_SUBTREE;
+ private boolean supportAltAuthentication;
+ private boolean supportHTTPBasicAuthentication = true;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void destroy() {
+ // TODO: We should release any resources maintained by the filter, such as connection pools.
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void doFilter(final ServletRequest request, final ServletResponse response,
+ final FilterChain chain) throws IOException, ServletException {
+ // Skip this filter if authentication has not been configured.
+ if (!isEnabled) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ // First of all parse the HTTP headers for authentication credentials.
+ if (!(request instanceof HttpServletRequest && response instanceof HttpServletResponse)) {
+ // This should never happen.
+ throw new ServletException("non-HTTP request or response");
+ }
+
+ // TODO: support logout, sessions, reauth?
+ final HttpServletRequest req = (HttpServletRequest) request;
+ final HttpServletResponse res = (HttpServletResponse) response;
+
+ /*
+ * Store the authenticated connection so that it can be re-used by the
+ * servlet if needed. However, make sure that it is closed on
+ * completion.
+ */
+ final AtomicReference<Connection> savedConnection = new AtomicReference<Connection>();
+ final CompletionHandler completionHandler =
+ CompletionHandlerFactory.getInstance(req.getServletContext())
+ .createCompletionHandler(req, res);
+ if (completionHandler.isAsynchronous()) {
+ completionHandler.addCompletionListener(new Runnable() {
+ @Override
+ public void run() {
+ closeConnection(savedConnection);
+ }
+ });
+ }
+
+ try {
+ final String headerUsername =
+ supportAltAuthentication ? req.getHeader(altAuthenticationUsernameHeader)
+ : null;
+ final String headerPassword =
+ supportAltAuthentication ? req.getHeader(altAuthenticationPasswordHeader)
+ : null;
+ final String headerAuthorization =
+ supportHTTPBasicAuthentication ? req.getHeader("Authorization") : null;
+
+ final String username;
+ final char[] password;
+ if (headerUsername != null) {
+ if (headerPassword == null || headerUsername.isEmpty() || headerPassword.isEmpty()) {
+ throw ResourceException.getException(401);
+ }
+ username = headerUsername;
+ password = headerPassword.toCharArray();
+ } else if (headerAuthorization != null) {
+ final StringTokenizer st = new StringTokenizer(headerAuthorization);
+ final String method = st.nextToken();
+ if (method == null || !method.equalsIgnoreCase(HttpServletRequest.BASIC_AUTH)) {
+ throw ResourceException.getException(401);
+ }
+ final String b64Credentials = st.nextToken();
+ if (b64Credentials == null) {
+ throw ResourceException.getException(401);
+ }
+ final String credentials = ByteString.valueOfBase64(b64Credentials).toString();
+ final String[] usernameAndPassword = credentials.split(":");
+ if (usernameAndPassword.length != 2) {
+ throw ResourceException.getException(401);
+ }
+ username = usernameAndPassword[0];
+ password = usernameAndPassword[1].toCharArray();
+ } else {
+ throw ResourceException.getException(401);
+ }
+
+ // If we've got here then we have a username and password.
+ switch (authenticationMethod) {
+ case SIMPLE:
+ bindLDAPConnectionFactory.getConnectionAsync(new ResultHandler<Connection>() {
+ @Override
+ public void handleErrorResult(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>() {
+
+ @Override
+ public void handleErrorResult(ErrorResultException error) {
+ completionHandler.onError(asResourceException(error));
+ }
+
+ @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.
+ final Map<String, Object> authzid =
+ new LinkedHashMap<String, Object>(2);
+ authzid.put(AUTHZID_DN, username);
+ 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));
+ }
+ }
+ });
+ }
+ });
+ break;
+ case SASL_PLAIN:
+ case SEARCH_SIMPLE:
+ throw ResourceException.getException(401);
+ }
+
+ /*
+ * Block until authentication completes if needed and then invoke
+ * the remainder of the filter chain.
+ */
+ if (!completionHandler.isAsynchronous()) {
+ completionHandler.awaitIfNeeded();
+ chain.doFilter(request, response);
+ closeConnection(savedConnection);
+ }
+ } catch (final Throwable t) {
+ // Complete and close the connection if needed.
+ completionHandler.onError(t);
+ if (!completionHandler.isAsynchronous()) {
+ closeConnection(savedConnection);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void init(final FilterConfig config) throws ServletException {
+ // FIXME: make it possible to configure the filter externally, especially
+ // connection factories.
+ final String configFileName = config.getInitParameter(INIT_PARAM_CONFIG_FILE);
+ if (configFileName == null) {
+ throw new ServletException("Authentication filter initialization parameter '"
+ + INIT_PARAM_CONFIG_FILE + "' not specified");
+ }
+ final InputStream configFile =
+ config.getServletContext().getResourceAsStream(configFileName);
+ if (configFile == null) {
+ throw new ServletException("Servlet filter configuration file '" + configFileName
+ + "' not found");
+ }
+ try {
+ // Parse the config file.
+ final Object content = JSON_MAPPER.readValue(configFile, Object.class);
+ if (!(content instanceof Map)) {
+ throw new ServletException("Servlet filter configuration file '" + configFileName
+ + "' does not contain a valid JSON configuration");
+ }
+
+ // Parse the authentication configuration.
+ final JsonValue configuration = new JsonValue(content);
+ final JsonValue authnConfig = configuration.get("authenticationFilter");
+ if (!authnConfig.isNull()) {
+ supportHTTPBasicAuthentication =
+ authnConfig.get("supportHTTPBasicAuthentication").required().asBoolean();
+
+ // Alternative HTTP authentication.
+ supportAltAuthentication =
+ authnConfig.get("supportAltAuthentication").required().asBoolean();
+ if (supportAltAuthentication) {
+ altAuthenticationUsernameHeader =
+ authnConfig.get("altAuthenticationUsernameHeader").required()
+ .asString();
+ altAuthenticationPasswordHeader =
+ authnConfig.get("altAuthenticationPasswordHeader").required()
+ .asString();
+ }
+
+ // Should the authenticated connection should be cached for use by subsequent LDAP operations?
+ reuseAuthenticatedConnection =
+ authnConfig.get("reuseAuthenticatedConnection").required().asBoolean();
+
+ // Parse the authentication method and associated parameters.
+ authenticationMethod = parseAuthenticationMethod(authnConfig);
+ switch (authenticationMethod) {
+ case SIMPLE:
+ // Nothing to do.
+ break;
+ case SASL_PLAIN:
+ saslAuthzIdTemplate =
+ authnConfig.get("saslAuthzIdTemplate").required().asString();
+ break;
+ case SEARCH_SIMPLE:
+ searchBaseDN =
+ DN.valueOf(authnConfig.get("searchBaseDN").required().asString(),
+ schema);
+ searchScope = parseSearchScope(authnConfig);
+ searchFilterTemplate =
+ authnConfig.get("searchFilterTemplate").required().asString();
+
+ // Parse the LDAP connection factory to be used for searches.
+ final String ldapFactoryName =
+ authnConfig.get("searchLDAPConnectionFactory").required().asString();
+ searchLDAPConnectionFactory =
+ Rest2LDAP.configureConnectionFactory(configuration.get(
+ "ldapConnectionFactories").required(), ldapFactoryName);
+ break;
+ }
+
+ // Parse the LDAP connection factory to be used for binds.
+ final String ldapFactoryName =
+ authnConfig.get("bindLDAPConnectionFactory").required().asString();
+ bindLDAPConnectionFactory =
+ Rest2LDAP.configureConnectionFactory(configuration.get(
+ "ldapConnectionFactories").required(), ldapFactoryName);
+
+ isEnabled = true;
+ }
+ } catch (final ServletException e) {
+ // Rethrow.
+ throw e;
+ } catch (final Exception e) {
+ throw new ServletException("Servlet filter configuration file '" + configFileName
+ + "' could not be read: " + e.getMessage());
+ } finally {
+ try {
+ configFile.close();
+ } catch (final Exception e) {
+ // Ignore.
+ }
+ }
+ }
+
+ private AuthenticationMethod parseAuthenticationMethod(final JsonValue configuration) {
+ if (configuration.isDefined("method")) {
+ final String method = configuration.get("method").asString();
+ if (method.equalsIgnoreCase("simple")) {
+ return AuthenticationMethod.SIMPLE;
+ } else if (method.equalsIgnoreCase("sasl-plain")) {
+ return AuthenticationMethod.SASL_PLAIN;
+ } else if (method.equalsIgnoreCase("search-simple")) {
+ return AuthenticationMethod.SEARCH_SIMPLE;
+ } else {
+ throw new JsonValueException(configuration,
+ "Illegal authentication method: must be either 'simple', "
+ + "'sasl-plain', or 'search-simple'");
+ }
+ } else {
+ return AuthenticationMethod.SEARCH_SIMPLE;
+ }
+ }
+
+ private SearchScope parseSearchScope(final JsonValue configuration) {
+ if (configuration.isDefined("searchScope")) {
+ final String scope = configuration.get("searchScope").asString();
+ if (scope.equalsIgnoreCase("sub")) {
+ return SearchScope.WHOLE_SUBTREE;
+ } else if (scope.equalsIgnoreCase("one")) {
+ return SearchScope.SINGLE_LEVEL;
+ } else {
+ throw new JsonValueException(configuration,
+ "Illegal search scope: must be either 'sub' or 'one'");
+ }
+ } else {
+ return SearchScope.WHOLE_SUBTREE;
+ }
+ }
+
+ private void closeConnection(final AtomicReference<Connection> savedConnection) {
+ final Connection connection = savedConnection.get();
+ if (connection != null) {
+ connection.close();
+ }
+ }
+
+}
diff --git a/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPConnectionFactoryProvider.java b/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPConnectionFactoryProvider.java
index 782bacd..276349b 100644
--- a/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPConnectionFactoryProvider.java
+++ b/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPConnectionFactoryProvider.java
@@ -102,7 +102,7 @@
for (final String mappingUrl : mappings.keys()) {
final JsonValue mapping = mappings.get(mappingUrl);
final CollectionResourceProvider provider =
- Rest2LDAP.builder().connectionFactory(ldapFactory).authorizationPolicy(
+ Rest2LDAP.builder().ldapConnectionFactory(ldapFactory).authorizationPolicy(
authzPolicy).proxyAuthzIdTemplate(proxyAuthzTemplate)
.configureMapping(mapping).build();
router.addRoute(mappingUrl, provider);
diff --git a/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPContextFactory.java b/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPContextFactory.java
index 330bfb4..0ef4e4d 100644
--- a/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPContextFactory.java
+++ b/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPContextFactory.java
@@ -170,9 +170,7 @@
*/
@Override
public Context createContext(final HttpServletRequest request) throws ResourceException {
- final Context securityContext =
- SecurityContextFactory.getHttpServletContextFactory().createContext(request);
- return createContext(securityContext, request);
+ return createContext(new RootContext(), request);
}
}
diff --git a/opendj3/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/web.xml b/opendj3/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/web.xml
index afc4e7f..2ba6b1c 100644
--- a/opendj3/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/web.xml
+++ b/opendj3/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/web.xml
@@ -18,12 +18,32 @@
<param-name>config-file</param-name>
<param-value>/opendj-rest2ldap-servlet.json</param-value>
</init-param>
+
+ <init-param>
+ <param-name>context-factory-class</param-name>
+ <param-value>org.forgerock.opendj.rest2ldap.servlet.Rest2LDAPContextFactory</param-value>
+ </init-param>
</servlet>
-
+
<servlet-mapping>
<servlet-name>OpenDJ Commons REST LDAP Gateway</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
+ <filter>
+ <filter-name>OpenDJ Commons REST LDAP Authentication Filter</filter-name>
+ <filter-class>org.forgerock.opendj.rest2ldap.servlet.Rest2LDAPAuthnFilter</filter-class>
+
+ <init-param>
+ <param-name>config-file</param-name>
+ <param-value>/opendj-rest2ldap-servlet.json</param-value>
+ </init-param>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>OpenDJ Commons REST LDAP Authentication Filter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
</web-app>
\ No newline at end of file
diff --git a/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json b/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
index 14645cc..cef1345 100644
--- a/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
+++ b/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
@@ -69,7 +69,7 @@
// "sasl-plain" - the username is an authzid which will be
// substituted into the "saslAuthzIdTemplate" using
// %s substitution
- // "search+simple" - the user's DN will be resolved by performing an
+ // "search-simple" - the user's DN will be resolved by performing an
// LDAP search using a filter constructed by
// substituting the username into the
// "searchFilterTemplate" using %s substitution.
@@ -84,11 +84,11 @@
"saslAuthzIdTemplate" : "dn:uid=%s,ou=people,dc=example,dc=com",
// The connection factory which will be used for performing LDAP
- // searches to locate users when "search+simple" authentication is
+ // searches to locate users when "search-simple" authentication is
// enabled.
"searchLDAPConnectionFactory" : "root",
- // The search parameters to use for "search+simple" authentication.
+ // The search parameters to use for "search-simple" authentication.
"searchBaseDN" : "ou=people,dc=example,dc=com",
"searchScope" : "sub", // Or "one".
"searchFilterTemplate" : "(&(objectClass=inetOrgPerson)(uid=%s))"
@@ -120,7 +120,7 @@
// derived from the "proxyAuthzIdTemplate". Proxied
// authorization will only be used if there is no
// pre-authenticated connection available.
- "authorizationPolicy" : "none",
+ "authorizationPolicy" : "proxy",
// The AuthzID template which will be used for proxied authorization.
// The template should contain fields which are expected to be found in
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
index f6c9579..ba92a5b 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
@@ -16,7 +16,7 @@
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
-import static org.forgerock.opendj.rest2ldap.Utils.adapt;
+import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import java.io.Closeable;
import java.util.LinkedHashMap;
@@ -302,7 +302,7 @@
config.connectionFactory().getConnectionAsync(new ResultHandler<Connection>() {
@Override
public final void handleErrorResult(final ErrorResultException error) {
- handler.handleError(adapt(error));
+ handler.handleError(asResourceException(error));
}
@Override
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
index b4eda51..ace3b59 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -18,8 +18,8 @@
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
import static org.forgerock.opendj.ldap.Filter.alwaysTrue;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
+import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
-import static org.forgerock.opendj.rest2ldap.Utils.adapt;
import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
import static org.forgerock.opendj.rest2ldap.Utils.transform;
@@ -251,7 +251,7 @@
@Override
public void handleErrorResult(final ErrorResultException error) {
- pendingResult.compareAndSet(null, adapt(error));
+ pendingResult.compareAndSet(null, asResourceException(error));
completeIfNecessary();
}
@@ -312,7 +312,7 @@
new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
@Override
public void handleErrorResult(final ErrorResultException error) {
- h.handleError(adapt(error));
+ h.handleError(asResourceException(error));
}
@Override
@@ -559,7 +559,7 @@
return new org.forgerock.opendj.ldap.ResultHandler<Result>() {
@Override
public void handleErrorResult(final ErrorResultException error) {
- handler.handleError(adapt(error));
+ handler.handleError(asResourceException(error));
}
@Override
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
index ae72b5f..51b3a09 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
@@ -17,8 +17,8 @@
import static java.util.Collections.singletonList;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
+import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
-import static org.forgerock.opendj.rest2ldap.Utils.adapt;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
import static org.forgerock.opendj.rest2ldap.Utils.transform;
import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_WRITE;
@@ -199,7 +199,7 @@
@Override
public void handleErrorResult(final ErrorResultException error) {
- h.handleError(adapt(error)); // Propagate.
+ h.handleError(asResourceException(error)); // Propagate.
}
@Override
@@ -234,7 +234,7 @@
readEntry(c, dn, h);
} catch (final Exception ex) {
// The LDAP attribute could not be decoded.
- h.handleError(adapt(ex));
+ h.handleError(asResourceException(ex));
}
} else {
try {
@@ -264,7 +264,7 @@
}
} catch (final Exception ex) {
// The LDAP attribute could not be decoded.
- h.handleError(adapt(ex));
+ h.handleError(asResourceException(ex));
}
}
}
@@ -373,7 +373,7 @@
.toString()
+ "' is ambiguous");
} catch (ErrorResultException e) {
- re = adapt(e);
+ re = asResourceException(e);
}
exception.compareAndSet(null, re);
completeIfNecessary();
@@ -433,7 +433,7 @@
@Override
public void handleErrorResult(final ErrorResultException error) {
if (!(error instanceof EntryNotFoundException)) {
- handler.handleError(adapt(error));
+ handler.handleError(asResourceException(error));
} else {
// The referenced entry does not exist so ignore it since it cannot be mapped.
handler.handleResult(null);
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index 9a7bed6..2ad567f 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -35,20 +35,29 @@
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.CollectionResourceProvider;
import org.forgerock.json.resource.ResourceException;
+import org.forgerock.opendj.ldap.AssertionFailureException;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
+import org.forgerock.opendj.ldap.AuthenticationException;
+import org.forgerock.opendj.ldap.AuthorizationException;
import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConnectionException;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.EntryNotFoundException;
+import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.FailoverLoadBalancingAlgorithm;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LinkedAttribute;
+import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
import org.forgerock.opendj.ldap.RDN;
+import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm;
import org.forgerock.opendj.ldap.SearchScope;
+import org.forgerock.opendj.ldap.TimeoutResultException;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
@@ -67,11 +76,11 @@
*/
public static final class Builder {
private final List<Attribute> additionalLDAPAttributes = new LinkedList<Attribute>();
+ private AuthorizationPolicy authzPolicy = AuthorizationPolicy.NONE;
private DN baseDN; // TODO: support template variables.
private ConnectionFactory factory;
private MVCCStrategy mvccStrategy;
private NameStrategy nameStrategy;
- private AuthorizationPolicy authzPolicy = AuthorizationPolicy.NONE;
private AuthzIdTemplate proxiedAuthzTemplate;
private ReadOnUpdatePolicy readOnUpdatePolicy = CONTROLS;
private AttributeMapper rootMapper;
@@ -91,6 +100,11 @@
return additionalLDAPAttribute(new LinkedAttribute(ad(attribute), values));
}
+ public Builder authorizationPolicy(final AuthorizationPolicy policy) {
+ this.authzPolicy = ensureNotNull(policy);
+ return this;
+ }
+
public Builder baseDN(final DN dn) {
ensureNotNull(dn);
this.baseDN = dn;
@@ -135,74 +149,9 @@
/**
* Configures the JSON to LDAP mapping using the provided JSON
* configuration. The caller is still required to set the connection
- * factory. The configuration should look like this, excluding the
- * C-like comments:
- *
- * <pre>
- * {
- * // The base DN beneath which LDAP entries are to be found.
- * "baseDN" : "ou=people,dc=example,dc=com",
- *
- * // The mechanism which should be used for read resources during updates, must be
- * // one of "disabled", "controls", or "search".
- * "readOnUpdatePolicy" : "controls",
- *
- * // Additional LDAP attributes which should be included with entries during add (create) operations.
- * "additionalLDAPAttributes" : [
- * {
- * "type" : "objectClass",
- * "values" : [
- * "top",
- * "person"
- * ]
- * }
- * ],
- *
- * // The strategy which should be used for deriving LDAP entry names from JSON resources.
- * "namingStrategy" : {
- * // Option 1) the RDN and resource ID are both derived from a single user attribute in the entry.
- * "strategy" : "clientDNNaming",
- * "dnAttribute" : "uid"
- *
- * // Option 2) the RDN and resource ID are derived from separate user attributes in the entry.
- * "strategy" : "clientNaming",
- * "dnAttribute" : "uid",
- * "idAttribute" : "mail"
- *
- * // Option 3) the RDN and is derived from a user attribute and the resource ID from an operational
- * // attribute in the entry.
- * "strategy" : "serverNaming",
- * "dnAttribute" : "uid",
- * "idAttribute" : "entryUUID"
- * },
- *
- * // The attribute which will be used for performing MVCC.
- * "etagAttribute" : "etag",
- *
- * // The JSON to LDAP attribute mappings.
- * "attributes" : {
- * "schemas" : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
- * "id" : { "simple" : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
- * "rev" : { "simple" : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
- * "userName" : { "simple" : { "ldapAttribute" : "mail", "isSingleValued" : true, "writability" : "readOnly" } },
- * "displayName" : { "simple" : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true } },
- * "name" : { "object" : {
- * "givenName" : { "simple" : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
- * "familyName" : { "simple" : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } },
- * },
- * "manager" : { "reference" : {
- * "ldapAttribute" : "manager",
- * "baseDN" : "ou=people,dc=example,dc=com",
- * "mapper" : { "object" : {
- * "id" : { "simple" : { "ldapAttribute" : "uid", "isSingleValued" : true } },
- * "displayName" : { "simple" : { "ldapAttribute" : "cn", "isSingleValued" : true } }
- * } }
- * },
- * ...
- * }
- * }
- * </pre>
- *
+ * factory. See the sample configuration file for a detailed description
+ * of its content.
+ *
* @param configuration
* The JSON configuration.
* @return A reference to this builder.
@@ -250,7 +199,7 @@
return this;
}
- public Builder connectionFactory(final ConnectionFactory factory) {
+ public Builder ldapConnectionFactory(final ConnectionFactory factory) {
this.factory = factory;
return this;
}
@@ -260,10 +209,15 @@
return this;
}
+ public Builder proxyAuthzIdTemplate(final String template) {
+ this.proxiedAuthzTemplate = template != null ? new AuthzIdTemplate(template) : null;
+ return this;
+ }
+
/**
* Sets the policy which should be used in order to read an entry before
* it is deleted, or after it is added or modified.
- *
+ *
* @param policy
* The policy which should be used in order to read an entry
* before it is deleted, or after it is added or modified.
@@ -277,7 +231,7 @@
/**
* Sets the schema which should be used when attribute types and
* controls.
- *
+ *
* @param schema
* The schema which should be used when attribute types and
* controls.
@@ -320,16 +274,6 @@
return useEtagAttribute(ad(attribute));
}
- public Builder authorizationPolicy(final AuthorizationPolicy policy) {
- this.authzPolicy = ensureNotNull(policy);
- return this;
- }
-
- public Builder proxyAuthzIdTemplate(final String template) {
- this.proxiedAuthzTemplate = template != null ? new AuthzIdTemplate(template) : null;
- return this;
- }
-
public Builder useServerEntryUUIDNaming(final AttributeType dnAttribute) {
return useServerNaming(dnAttribute, AttributeDescription
.create(getEntryUUIDAttributeType()));
@@ -551,61 +495,60 @@
}
+ /**
+ * Adapts a {@code Throwable} to a {@code ResourceException}. If the
+ * {@code Throwable} is an LDAP {@code ErrorResultException} then an
+ * appropriate {@code ResourceException} is returned, otherwise an
+ * {@code InternalServerErrorException} is returned.
+ *
+ * @param t
+ * The {@code Throwable} to be converted.
+ * @return The equivalent resource exception.
+ */
+ public static ResourceException asResourceException(final Throwable t) {
+ int resourceResultCode;
+ try {
+ throw t;
+ } catch (final ResourceException e) {
+ return e;
+ } catch (final AssertionFailureException e) {
+ resourceResultCode = ResourceException.VERSION_MISMATCH;
+ } catch (final AuthenticationException e) {
+ resourceResultCode = 401;
+ } catch (final AuthorizationException e) {
+ resourceResultCode = ResourceException.FORBIDDEN;
+ } catch (final ConnectionException e) {
+ resourceResultCode = ResourceException.UNAVAILABLE;
+ } catch (final EntryNotFoundException e) {
+ resourceResultCode = ResourceException.NOT_FOUND;
+ } catch (final MultipleEntriesFoundException e) {
+ resourceResultCode = ResourceException.INTERNAL_ERROR;
+ } catch (final TimeoutResultException e) {
+ resourceResultCode = 408;
+ } catch (final ErrorResultException e) {
+ final ResultCode rc = e.getResult().getResultCode();
+ if (rc.equals(ResultCode.ADMIN_LIMIT_EXCEEDED)) {
+ resourceResultCode = 413; // Request Entity Too Large
+ } else if (rc.equals(ResultCode.SIZE_LIMIT_EXCEEDED)) {
+ resourceResultCode = 413; // Request Entity Too Large
+ } else {
+ resourceResultCode = ResourceException.INTERNAL_ERROR;
+ }
+ } catch (final Throwable tmp) {
+ resourceResultCode = ResourceException.INTERNAL_ERROR;
+ }
+ return ResourceException.getException(resourceResultCode, t.getMessage(), t);
+ }
+
public static Builder builder() {
return new Builder();
}
/**
* Creates a new connection factory using the named configuration in the
- * provided JSON list of factory configurations. Excluding the C-like
- * comments, the configuration should look like this:
- *
- * <pre>
- * {
- * // A default pool of servers using no authentication.
- * "default" : {
- * // The primary data center, must contain at least one LDAP server.
- * "primaryLDAPServers" : [
- * {
- * "hostname" : "host1.example.com",
- * "port" : 389
- * },
- * {
- * "hostname" : "host2.example.com",
- * "port" : 389
- * },
- * ],
- *
- * // The optional secondary (fail-over) data center.
- * "secondaryLDAPServers" : [
- * {
- * "hostname" : "host3.example.com",
- * "port" : 389
- * },
- * {
- * "hostname" : "host4.example.com",
- * "port" : 389
- * },
- * ],
- *
- * // Connection pool configuration.
- * "connectionPoolSize" : 10,
- * "heartBeatIntervalSeconds" : 30
- * },
- *
- * // The same pool of servers except authenticated as cn=directory manager.
- * "root" : {
- * "inheritFrom" : "default",
- * "authentication" : {
- * "simple" : {
- * "bindDN" : "cn=directory manager",
- * "bindPassword" : "password"
- * }
- * }
- * }
- * }
- * </pre>
- *
+ * provided JSON list of factory configurations. See the sample
+ * configuration file for a detailed description of its content.
+ *
* @param configuration
* The JSON configuration.
* @param name
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
index 2297aae..4706fbe 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -21,7 +21,7 @@
import static java.util.Collections.singletonList;
import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
import static org.forgerock.opendj.ldap.Functions.fixedFunction;
-import static org.forgerock.opendj.rest2ldap.Utils.adapt;
+import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import static org.forgerock.opendj.rest2ldap.Utils.base64ToByteString;
import static org.forgerock.opendj.rest2ldap.Utils.byteStringToBase64;
import static org.forgerock.opendj.rest2ldap.Utils.byteStringToJson;
@@ -183,7 +183,8 @@
final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
if (jsonAttribute.isEmpty()) {
try {
- final ByteString va = valueAssertion != null ? encoder().apply(valueAssertion, null) : null;
+ final ByteString va =
+ valueAssertion != null ? encoder().apply(valueAssertion, null) : null;
h.handleResult(toFilter(c, type, ldapAttributeName.toString(), va));
} catch (Exception e) {
// Invalid assertion value - bad request.
@@ -212,7 +213,7 @@
h.handleResult(value != null ? new JsonValue(value) : null);
} catch (Exception ex) {
// The LDAP attribute could not be decoded.
- h.handleError(adapt(ex));
+ h.handleError(asResourceException(ex));
}
}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
index f8f5c14..c2d87d2 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -26,6 +26,7 @@
import static org.forgerock.opendj.ldap.schema.CoreSchema.getBooleanSyntax;
import static org.forgerock.opendj.ldap.schema.CoreSchema.getGeneralizedTimeSyntax;
import static org.forgerock.opendj.ldap.schema.CoreSchema.getIntegerSyntax;
+import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
import java.util.ArrayList;
import java.util.Collection;
@@ -36,22 +37,13 @@
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResultHandler;
-import org.forgerock.opendj.ldap.AssertionFailureException;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
-import org.forgerock.opendj.ldap.AuthenticationException;
-import org.forgerock.opendj.ldap.AuthorizationException;
import org.forgerock.opendj.ldap.ByteString;
-import org.forgerock.opendj.ldap.ConnectionException;
-import org.forgerock.opendj.ldap.EntryNotFoundException;
-import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.Function;
import org.forgerock.opendj.ldap.GeneralizedTime;
import org.forgerock.opendj.ldap.LinkedAttribute;
-import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
-import org.forgerock.opendj.ldap.ResultCode;
-import org.forgerock.opendj.ldap.TimeoutResultException;
import org.forgerock.opendj.ldap.schema.Syntax;
/**
@@ -187,51 +179,6 @@
return new AccumulatingResultHandler<V>(size, handler);
}
- /**
- * Adapts a {@code Throwable} to a {@code ResourceException}. If the
- * {@code Throwable} is an LDAP {@code ErrorResultException} then an
- * appropriate {@code ResourceException} is returned, otherwise an
- * {@code InternalServerErrorException} is returned.
- *
- * @param t
- * The {@code Throwable} to be converted.
- * @return The equivalent resource exception.
- */
- static ResourceException adapt(final Throwable t) {
- int resourceResultCode;
- try {
- throw t;
- } catch (final ResourceException e) {
- return e;
- } catch (final AssertionFailureException e) {
- resourceResultCode = ResourceException.VERSION_MISMATCH;
- } catch (final AuthenticationException e) {
- resourceResultCode = 401;
- } catch (final AuthorizationException e) {
- resourceResultCode = ResourceException.FORBIDDEN;
- } catch (final ConnectionException e) {
- resourceResultCode = ResourceException.UNAVAILABLE;
- } catch (final EntryNotFoundException e) {
- resourceResultCode = ResourceException.NOT_FOUND;
- } catch (final MultipleEntriesFoundException e) {
- resourceResultCode = ResourceException.INTERNAL_ERROR;
- } catch (final TimeoutResultException e) {
- resourceResultCode = 408;
- } catch (final ErrorResultException e) {
- final ResultCode rc = e.getResult().getResultCode();
- if (rc.equals(ResultCode.ADMIN_LIMIT_EXCEEDED)) {
- resourceResultCode = 413; // Request Entity Too Large
- } else if (rc.equals(ResultCode.SIZE_LIMIT_EXCEEDED)) {
- resourceResultCode = 413; // Request Entity Too Large
- } else {
- resourceResultCode = ResourceException.INTERNAL_ERROR;
- }
- } catch (final Throwable tmp) {
- resourceResultCode = ResourceException.INTERNAL_ERROR;
- }
- return ResourceException.getException(resourceResultCode, t.getMessage(), t);
- }
-
static Object attributeToJson(final Attribute a) {
final Function<ByteString, Object, Void> f =
fixedFunction(BYTESTRING_TO_JSON, a.getAttributeDescription());
@@ -376,7 +323,7 @@
try {
handler.handleResult(f.apply(result, null));
} catch (Throwable t) {
- handler.handleError(adapt(t));
+ handler.handleError(asResourceException(t));
}
}
};
--
Gitblit v1.10.0