opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPAuthnFilter.java
New file @@ -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(); } } } 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); 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); } } 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> 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 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 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 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); 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 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)); } } 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)); } } };