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 2010–2011 ApexIdentity Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017
018package org.forgerock.openig.http;
019
020import static java.lang.String.format;
021import static org.forgerock.http.filter.Filters.newSessionFilter;
022import static org.forgerock.http.handler.Handlers.chainOf;
023import static org.forgerock.http.protocol.Response.newResponsePromise;
024import static org.forgerock.http.routing.RouteMatchers.requestUriMatcher;
025import static org.forgerock.http.routing.RoutingMode.EQUALS;
026import static org.forgerock.http.routing.RoutingMode.STARTS_WITH;
027import static org.forgerock.http.util.Json.readJsonLenient;
028import static org.forgerock.json.JsonValue.array;
029import static org.forgerock.json.JsonValue.field;
030import static org.forgerock.json.JsonValue.json;
031import static org.forgerock.json.JsonValue.object;
032import static org.forgerock.openig.heap.Keys.API_PROTECTION_FILTER_HEAP_KEY;
033import static org.forgerock.openig.heap.Keys.AUDIT_HEAP_KEY;
034import static org.forgerock.openig.heap.Keys.AUDIT_SYSTEM_HEAP_KEY;
035import static org.forgerock.openig.heap.Keys.BASEURI_HEAP_KEY;
036import static org.forgerock.openig.heap.Keys.CAPTURE_HEAP_KEY;
037import static org.forgerock.openig.heap.Keys.CLIENT_HANDLER_HEAP_KEY;
038import static org.forgerock.openig.heap.Keys.ENDPOINT_REGISTRY_HEAP_KEY;
039import static org.forgerock.openig.heap.Keys.ENVIRONMENT_HEAP_KEY;
040import static org.forgerock.openig.heap.Keys.FORGEROCK_CLIENT_HANDLER_HEAP_KEY;
041import static org.forgerock.openig.heap.Keys.LOGSINK_HEAP_KEY;
042import static org.forgerock.openig.heap.Keys.SESSION_FACTORY_HEAP_KEY;
043import static org.forgerock.openig.heap.Keys.TEMPORARY_STORAGE_HEAP_KEY;
044import static org.forgerock.openig.heap.Keys.TIMER_HEAP_KEY;
045import static org.forgerock.openig.heap.Keys.TIME_SERVICE_HEAP_KEY;
046import static org.forgerock.openig.heap.Keys.TRANSACTION_ID_OUTBOUND_FILTER_HEAP_KEY;
047
048import java.io.File;
049import java.io.FileNotFoundException;
050import java.io.IOException;
051import java.io.InputStream;
052import java.net.InetAddress;
053import java.net.MalformedURLException;
054import java.net.URL;
055import java.net.UnknownHostException;
056import java.util.ArrayList;
057import java.util.List;
058
059import org.forgerock.http.Filter;
060import org.forgerock.http.Handler;
061import org.forgerock.http.HttpApplication;
062import org.forgerock.http.HttpApplicationException;
063import org.forgerock.http.filter.TransactionIdOutboundFilter;
064import org.forgerock.http.io.Buffer;
065import org.forgerock.http.protocol.Request;
066import org.forgerock.http.protocol.Response;
067import org.forgerock.http.protocol.Status;
068import org.forgerock.http.routing.Router;
069import org.forgerock.http.session.SessionManager;
070import org.forgerock.json.JsonValue;
071import org.forgerock.openig.audit.AuditSystem;
072import org.forgerock.openig.audit.decoration.AuditDecorator;
073import org.forgerock.openig.audit.internal.ForwardingAuditSystem;
074import org.forgerock.openig.config.Environment;
075import org.forgerock.openig.decoration.baseuri.BaseUriDecorator;
076import org.forgerock.openig.decoration.capture.CaptureDecorator;
077import org.forgerock.openig.decoration.timer.TimerDecorator;
078import org.forgerock.openig.filter.Chain;
079import org.forgerock.openig.handler.ClientHandler;
080import org.forgerock.openig.handler.Handlers;
081import org.forgerock.openig.heap.HeapImpl;
082import org.forgerock.openig.heap.Name;
083import org.forgerock.openig.io.TemporaryStorage;
084import org.forgerock.openig.log.ConsoleLogSink;
085import org.forgerock.openig.log.LogSink;
086import org.forgerock.openig.log.LogSinkLoggerFactory;
087import org.forgerock.openig.log.Logger;
088import org.forgerock.services.context.ClientContext;
089import org.forgerock.services.context.Context;
090import org.forgerock.util.Factory;
091import org.forgerock.util.promise.NeverThrowsException;
092import org.forgerock.util.promise.Promise;
093import org.forgerock.util.time.TimeService;
094import org.slf4j.ILoggerFactory;
095import org.slf4j.LoggerFactory;
096
097/**
098 * Configuration class for configuring the OpenIG Gateway.
099 *
100 * @since 3.1.0
101 */
102@SuppressWarnings("deprecation")
103public final class GatewayHttpApplication implements HttpApplication {
104    /**
105     * {@link Logger} instance for the openig-war module.
106     */
107    static final org.slf4j.Logger LOG = LoggerFactory.getLogger(GatewayHttpApplication.class);
108
109    private static final JsonValue DEFAULT_CLIENT_HANDLER =
110                                        json(object(field("name", CLIENT_HANDLER_HEAP_KEY),
111                                                    field("type", ClientHandler.class.getName())));
112
113    private static final JsonValue FORGEROCK_CLIENT_HANDLER =
114                                json(object(field("name", FORGEROCK_CLIENT_HANDLER_HEAP_KEY),
115                                            field("type", Chain.class.getName()),
116                                            field("config", object(
117                                                   field("filters", array(TRANSACTION_ID_OUTBOUND_FILTER_HEAP_KEY)),
118                                                   field("handler", CLIENT_HANDLER_HEAP_KEY)))));
119
120    private HeapImpl heap;
121    private TemporaryStorage storage;
122    private Environment environment;
123
124    /**
125     * Default constructor called by the HTTP Framework.
126     */
127    public GatewayHttpApplication() {
128        this(new GatewayEnvironment());
129    }
130
131    /**
132     * Constructor for tests.
133     */
134    GatewayHttpApplication(final Environment environment) {
135        this.environment = environment;
136    }
137
138    @Override
139    public Handler start() throws HttpApplicationException {
140        if (heap != null) {
141            throw new HttpApplicationException("Gateway already started.");
142        }
143
144        try {
145            // Load the configuration
146            final URL configurationURL = selectConfigurationUrl();
147            final JsonValue config = readJson(configurationURL);
148            TimeService timeService = TimeService.SYSTEM;
149
150            // Create and configure the heap
151            heap = new HeapImpl(Name.of(configurationURL.toString()));
152
153            // Provide the base tree:
154            // /openig/api/system/objects
155            Router openigRouter = new Router();
156            Router apiRouter = new Router();
157            Router systemRouter = new Router();
158            Router systemObjectsRouter = new Router();
159            addSubRouter(openigRouter, "api", apiRouter);
160            addSubRouter(apiRouter, "system", systemRouter);
161            // TODO Could be removed after OPENIG-425 has been implemented
162            // this is just to mimic the fact that 'system' should be a Route within a RouterHandler
163            addSubRouter(systemRouter, "objects", systemObjectsRouter);
164            systemObjectsRouter.addRoute(requestUriMatcher(EQUALS, ""), Handlers.NO_CONTENT);
165            heap.put(ENDPOINT_REGISTRY_HEAP_KEY, new EndpointRegistry(systemObjectsRouter,
166                                                                      "/openig/api/system/objects"));
167
168            // "Live" objects
169            heap.put(ENVIRONMENT_HEAP_KEY, environment);
170            heap.put(TIME_SERVICE_HEAP_KEY, timeService);
171
172            AuditSystem auditSystem = new ForwardingAuditSystem();
173
174            // can be overridden in config
175            heap.put(TEMPORARY_STORAGE_HEAP_KEY, new TemporaryStorage());
176            heap.put(LOGSINK_HEAP_KEY, new ConsoleLogSink());
177            heap.put(CAPTURE_HEAP_KEY, new CaptureDecorator(null, false, false));
178            heap.put(TIMER_HEAP_KEY, new TimerDecorator());
179            heap.put(AUDIT_HEAP_KEY, new AuditDecorator(auditSystem));
180            heap.put(BASEURI_HEAP_KEY, new BaseUriDecorator());
181            heap.put(AUDIT_SYSTEM_HEAP_KEY, auditSystem);
182            heap.put(TRANSACTION_ID_OUTBOUND_FILTER_HEAP_KEY, new TransactionIdOutboundFilter());
183            heap.addDefaultDeclaration(DEFAULT_CLIENT_HANDLER);
184            heap.addDefaultDeclaration(FORGEROCK_CLIENT_HANDLER);
185            heap.init(config, "logSink", "temporaryStorage", "handler", "handlerObject", "globalDecorators");
186
187            // As all heaplets can specify their own storage and logger,
188            // these two lines provide custom logger or storage available.
189            LogSink logSink = heap.resolve(config.get("logSink").defaultTo(LOGSINK_HEAP_KEY), LogSink.class, true);
190            final Logger logger = new Logger(logSink, Name.of(GatewayHttpApplication.class));
191            storage = heap.resolve(config.get("temporaryStorage").defaultTo(TEMPORARY_STORAGE_HEAP_KEY),
192                    TemporaryStorage.class);
193
194            // Protect the /openig namespace
195            Filter protector = heap.get(API_PROTECTION_FILTER_HEAP_KEY, Filter.class);
196            if (protector == null) {
197                protector = new LoopbackAddressOnlyFilter(logger);
198            }
199            Handler restricted = chainOf(openigRouter, protector);
200
201            Router router = new Router();
202            router.addRoute(requestUriMatcher(STARTS_WITH, "openig"), restricted);
203
204            // Create the root handler.
205            List<Filter> filters = new ArrayList<>();
206
207            // Let the user override the container's session.
208            final SessionManager sessionManager = heap.get(SESSION_FACTORY_HEAP_KEY, SessionManager.class);
209            if (sessionManager != null) {
210                filters.add(newSessionFilter(sessionManager));
211            }
212
213            // Create the root handler
214            Handler rootHandler = chainOf(heap.getHandler(), filters);
215            router.setDefaultRoute(rootHandler);
216
217            // Configure SLF4J with the LogSink defined by the user
218            ILoggerFactory factory = LoggerFactory.getILoggerFactory();
219            if (factory instanceof LogSinkLoggerFactory) {
220                ((LogSinkLoggerFactory) factory).setLogSink(logSink);
221            }
222            return router;
223        } catch (Exception e) {
224            LOG.error("Failed to initialise Http Application", e);
225            // Release resources
226            stop();
227            throw new HttpApplicationException("Unable to start OpenIG", e);
228        }
229    }
230
231    private URL selectConfigurationUrl() throws MalformedURLException {
232        LOG.info("OpenIG base directory : {}", environment.getBaseDirectory());
233
234        final File configuration = new File(environment.getConfigDirectory(), "config.json");
235        final URL configurationURL;
236        if (configuration.canRead()) {
237            LOG.info("Reading the configuration from {}", configuration.getAbsolutePath());
238            configurationURL = configuration.toURI().toURL();
239        } else {
240            LOG.info("{} not readable, using OpenIG default configuration", configuration.getAbsolutePath());
241            configurationURL = getClass().getResource("default-config.json");
242        }
243        return configurationURL;
244    }
245
246    private static void addSubRouter(final Router base, final String name, final Handler router) {
247        base.addRoute(requestUriMatcher(EQUALS, ""), Handlers.NO_CONTENT);
248        base.addRoute(requestUriMatcher(STARTS_WITH, name), router);
249    }
250
251    @Override
252    public Factory<Buffer> getBufferFactory() {
253        return storage;
254    }
255
256    @Override
257    public void stop() {
258        if (heap != null) {
259            // Try to release Heaplet(s) resources
260            heap.destroy();
261            heap = null;
262        }
263    }
264
265    private static JsonValue readJson(URL resource) throws IOException {
266        try (InputStream in = resource.openStream()) {
267            return new JsonValue(readJsonLenient(in));
268        } catch (FileNotFoundException e) {
269            throw new IOException(format("File %s does not exist", resource), e);
270        }
271    }
272
273    /**
274     * Permits only local clients to access /openig endpoint.
275     */
276    private static class LoopbackAddressOnlyFilter implements Filter {
277        private final Logger logger;
278
279        public LoopbackAddressOnlyFilter(final Logger logger) {
280            this.logger = logger;
281        }
282
283        @Override
284        public Promise<Response, NeverThrowsException> filter(final Context context,
285                                                              final Request request,
286                                                              final Handler next) {
287            ClientContext client = context.asContext(ClientContext.class);
288            String remoteAddr = client.getRemoteAddress();
289            try {
290                // Accept any address that is bound to loop-back
291                InetAddress[] addresses = InetAddress.getAllByName(remoteAddr);
292                for (InetAddress address : addresses) {
293                    if (address.isLoopbackAddress()) {
294                        return next.handle(context, request);
295                    }
296                }
297            } catch (UnknownHostException e) {
298                logger.trace(format("Cannot resolve host '%s' when accessing '/openig'", remoteAddr));
299            }
300            return newResponsePromise(new Response(Status.FORBIDDEN));
301        }
302    }
303}