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}