001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2015 ForgeRock AS.
015 */
016
017package org.forgerock.opendj.rest2ldap;
018
019import static org.forgerock.http.util.Json.*;
020import static org.forgerock.opendj.rest2ldap.Rest2LDAP.configureConnectionFactory;
021import static org.forgerock.util.Utils.*;
022
023import java.io.Closeable;
024import java.io.IOException;
025import java.io.InputStream;
026import java.net.URL;
027
028import org.forgerock.http.Handler;
029import org.forgerock.http.HttpApplication;
030import org.forgerock.http.HttpApplicationException;
031import org.forgerock.http.handler.Handlers;
032import org.forgerock.http.io.Buffer;
033import org.forgerock.http.protocol.Request;
034import org.forgerock.http.protocol.Response;
035import org.forgerock.json.JsonValue;
036import org.forgerock.json.resource.CollectionResourceProvider;
037import org.forgerock.json.resource.RequestHandler;
038import org.forgerock.json.resource.Router;
039import org.forgerock.json.resource.http.CrestHttp;
040import org.forgerock.opendj.ldap.ConnectionFactory;
041import org.forgerock.services.context.Context;
042import org.forgerock.util.Factory;
043import org.forgerock.util.Reject;
044import org.forgerock.util.promise.NeverThrowsException;
045import org.forgerock.util.promise.Promise;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049/** Rest2ldap HTTP application. */
050public final class Rest2LDAPHttpApplication implements HttpApplication {
051    private static final Logger LOG = LoggerFactory.getLogger(Rest2LDAPHttpApplication.class);
052
053    private static final class HttpHandler implements Handler, Closeable {
054        private final ConnectionFactory ldapConnectionFactory;
055        private final Handler delegate;
056
057        HttpHandler(final JsonValue configuration) {
058            ldapConnectionFactory = createLdapConnectionFactory(configuration);
059            try {
060                delegate = CrestHttp.newHttpHandler(createRouter(configuration, ldapConnectionFactory));
061            } catch (final RuntimeException e) {
062                closeSilently(ldapConnectionFactory);
063                throw e;
064            }
065        }
066
067        private static RequestHandler createRouter(
068                final JsonValue configuration, final ConnectionFactory ldapConnectionFactory) {
069            final AuthorizationPolicy authzPolicy = configuration.get("servlet")
070                    .get("authorizationPolicy")
071                    .required()
072                    .asEnum(AuthorizationPolicy.class);
073            final String proxyAuthzTemplate = configuration.get("servlet").get("proxyAuthzIdTemplate").asString();
074            final JsonValue mappings = configuration.get("servlet").get("mappings").required();
075
076            final Router router = new Router();
077            for (final String mappingUrl : mappings.keys()) {
078                final JsonValue mapping = mappings.get(mappingUrl);
079                final CollectionResourceProvider provider = Rest2LDAP.builder()
080                        .ldapConnectionFactory(ldapConnectionFactory)
081                        .authorizationPolicy(authzPolicy)
082                        .proxyAuthzIdTemplate(proxyAuthzTemplate)
083                        .configureMapping(mapping)
084                        .build();
085                router.addRoute(Router.uriTemplate(mappingUrl), provider);
086            }
087            return router;
088        }
089
090        private static ConnectionFactory createLdapConnectionFactory(final JsonValue configuration) {
091            final String ldapFactoryName = configuration.get("servlet").get("ldapConnectionFactory").asString();
092            if (ldapFactoryName != null) {
093                return configureConnectionFactory(
094                        configuration.get("ldapConnectionFactories").required(), ldapFactoryName);
095            }
096            return null;
097        }
098
099        @Override
100        public void close() {
101            closeSilently(ldapConnectionFactory);
102        }
103
104        @Override
105        public Promise<Response, NeverThrowsException> handle(final Context context, final Request request) {
106            return delegate.handle(context, request);
107        }
108    }
109
110    private final URL configurationUrl;
111    private HttpHandler handler;
112    private HttpAuthenticationFilter filter;
113
114    /**
115     * Default constructor called by the HTTP Framework which will use the
116     * default configuration file location.
117     */
118    public Rest2LDAPHttpApplication() {
119        this.configurationUrl = getClass().getResource("/opendj-rest2ldap-config.json");
120    }
121
122    /**
123     * Creates a new Rest2LDAP HTTP application using the provided configuration URL.
124     *
125     * @param configurationURL
126     *            The URL to the JSON configuration file.
127     */
128    public Rest2LDAPHttpApplication(final URL configurationURL) {
129        Reject.ifNull(configurationURL, "The configuration URL must not be null");
130        this.configurationUrl = configurationURL;
131    }
132
133    private static JsonValue readJson(final URL resource) throws IOException {
134        try (InputStream in = resource.openStream()) {
135            return new JsonValue(readJsonLenient(in));
136        }
137    }
138
139    @Override
140    public Handler start() throws HttpApplicationException {
141        try {
142            final JsonValue configuration = readJson(configurationUrl);
143            handler = new HttpHandler(configuration);
144            filter = new HttpAuthenticationFilter(configuration);
145            return Handlers.chainOf(handler, filter);
146        } catch (final Exception e) {
147            // TODO i18n, once supported in opendj-rest2ldap
148            final String errorMsg = "Unable to start Rest2Ldap Http Application";
149            LOG.error(errorMsg, e);
150            stop();
151            throw new HttpApplicationException(errorMsg, e);
152        }
153    }
154
155    @Override
156    public Factory<Buffer> getBufferFactory() {
157        // Use container default buffer factory.
158        return null;
159    }
160
161    @Override
162    public void stop() {
163        closeSilently(handler, filter);
164        handler = null;
165        filter = null;
166    }
167}