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 2009 Sun Microsystems Inc. 015 * Portions Copyright 2010-2011 ApexIdentity Inc. 016 * Portions Copyright 2011-2015 ForgeRock AS. 017 */ 018 019package org.forgerock.openig.handler; 020 021import static java.lang.String.format; 022import static org.forgerock.http.handler.HttpClientHandler.OPTION_CONNECT_TIMEOUT; 023import static org.forgerock.http.handler.HttpClientHandler.OPTION_HOSTNAME_VERIFIER; 024import static org.forgerock.http.handler.HttpClientHandler.OPTION_KEY_MANAGERS; 025import static org.forgerock.http.handler.HttpClientHandler.OPTION_MAX_CONNECTIONS; 026import static org.forgerock.http.handler.HttpClientHandler.OPTION_RETRY_REQUESTS; 027import static org.forgerock.http.handler.HttpClientHandler.OPTION_REUSE_CONNECTIONS; 028import static org.forgerock.http.handler.HttpClientHandler.OPTION_SO_TIMEOUT; 029import static org.forgerock.http.handler.HttpClientHandler.OPTION_SSLCONTEXT_ALGORITHM; 030import static org.forgerock.http.handler.HttpClientHandler.OPTION_SSL_CIPHER_SUITES; 031import static org.forgerock.http.handler.HttpClientHandler.OPTION_SSL_ENABLED_PROTOCOLS; 032import static org.forgerock.http.handler.HttpClientHandler.OPTION_TEMPORARY_STORAGE; 033import static org.forgerock.http.handler.HttpClientHandler.OPTION_TRUST_MANAGERS; 034import static org.forgerock.openig.util.JsonValues.ofRequiredHeapObject; 035import static org.forgerock.util.Utils.closeSilently; 036import static org.forgerock.util.time.Duration.duration; 037 038import java.util.ArrayList; 039import java.util.List; 040 041import javax.net.ssl.KeyManager; 042import javax.net.ssl.TrustManager; 043 044import org.forgerock.http.Handler; 045import org.forgerock.http.HttpApplicationException; 046import org.forgerock.http.apache.async.AsyncHttpClientProvider; 047import org.forgerock.http.handler.HttpClientHandler; 048import org.forgerock.http.protocol.Request; 049import org.forgerock.http.protocol.Response; 050import org.forgerock.json.JsonValue; 051import org.forgerock.openig.heap.GenericHeapObject; 052import org.forgerock.openig.heap.GenericHeaplet; 053import org.forgerock.openig.heap.HeapException; 054import org.forgerock.services.context.Context; 055import org.forgerock.util.Options; 056import org.forgerock.util.promise.NeverThrowsException; 057import org.forgerock.util.promise.Promise; 058import org.forgerock.util.promise.ResultHandler; 059import org.forgerock.util.time.Duration; 060 061/** 062 * Submits requests to remote servers. In this implementation, requests are 063 * dispatched through a CHF {@link org.forgerock.http.spi.HttpClient}. 064 * 065 * 066 * <pre> 067 * {@code 068 * { 069 * "name": "ClientHandler", 070 * "type": "ClientHandler", 071 * "config": { 072 * "connections": 64, 073 * "disableReuseConnection": true, 074 * "disableRetries": true, 075 * "hostnameVerifier": "ALLOW_ALL", 076 * "sslContextAlgorithm": "TLS", 077 * "soTimeout": "10 seconds", 078 * "connectionTimeout": "10 seconds", 079 * "numberOfWorkers": 6, 080 * "keyManager": [ "RefToKeyManager", ... ], 081 * "trustManager": [ "RefToTrustManager", ... ], 082 * "sslEnabledProtocols": [ "SSLv2", ... ], 083 * "sslCipherSuites": [ "TLS_DH_anon_WITH_AES_256_CBC_SHA256", ... ] 084 * } 085 * } 086 * } 087 * </pre> 088 * 089 * <strong>Note:</strong> This implementation does not verify hostnames for 090 * outgoing SSL connections by default. This is because the gateway will usually access the 091 * SSL endpoint using a raw IP address rather than a fully-qualified hostname. 092 * <br> 093 * It's possible to override this behavior using the {@literal hostnameVerifier} attribute (case is not important, 094 * but unknown values will produce an error). 095 * <br> 096 * Accepted values are: 097 * <ul> 098 * <li>{@literal ALLOW_ALL} (the default)</li> 099 * <li>{@literal STRICT}</li> 100 * </ul> 101 * <br> 102 * The {@literal sslContextAlgorithm} optional attribute used to set the SSL Context Algorithm for SSL/TLS 103 * connections, it defaults to {@literal TLS}. See the JavaSE docs for the full list of supported 104 * <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext">values.</a> 105 * <br> 106 * The {@literal keyManager} and {@literal trustManager} optional attributes are referencing a 107 * list of {@link KeyManager} (and {@link TrustManager} respectively). They support singleton value (use a single 108 * reference) as well as multi-valued references (a list): 109 * <pre> 110 * {@code 111 * "keyManager": "SingleKeyManagerReference", 112 * "trustManager": [ "RefOne", "RefTwo" ] 113 * } 114 * </pre> 115 * 116 * The {@literal soTimeout} optional attribute specifies a socket timeout (the given amount of time a connection 117 * will live before being considered a stalled and automatically destroyed). It defaults to {@literal 10 seconds}. 118 * <br> 119 * The {@literal connectionTimeout} optional attribute specifies a connection timeout (the given amount of time to 120 * wait until the connection is established). It defaults to {@literal 10 seconds}. 121 * 122 * <p>The {@literal numberOfWorkers} optional attribute specifies the number of threads dedicated to process outgoing 123 * requests. It defaults to the number of CPUs available to the JVM. This attribute is only used if an asynchronous 124 * Http client engine is used (that is the default). 125 * 126 * <p>The {@literal sslEnabledProtocols} optional attribute specifies 127 * <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#jssenames">the protocol 128 * versions</a> to be enabled for use on the connection. 129 * 130 * <p>The {@literal sslCipherSuites} optional attribute specifies 131 * <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#ciphersuites"> 132 * cipher suite names</a> used by the SSL connection. 133 * 134 * @see Duration 135 * @see org.forgerock.openig.security.KeyManagerHeaplet 136 * @see org.forgerock.openig.security.TrustManagerHeaplet 137 */ 138public class ClientHandler extends GenericHeapObject implements Handler { 139 140 private final Handler delegate; 141 142 /** 143 * Creates a new client handler. 144 * 145 * @param delegate 146 * The HTTP Handler delegate. 147 */ 148 public ClientHandler(final Handler delegate) { 149 this.delegate = delegate; 150 } 151 152 @Override 153 public Promise<Response, NeverThrowsException> handle(final Context context, final Request request) { 154 return delegate.handle(context, request) 155 .thenOnResult(new ResultHandler<Response>() { 156 @Override 157 public void handleResult(final Response response) { 158 if (response.getCause() != null) { 159 logger.warning(response.getCause()); 160 } 161 } 162 }); 163 } 164 165 /** Creates and initializes a client handler in a heap environment. */ 166 public static class Heaplet extends GenericHeaplet { 167 168 private HttpClientHandler httpClientHandler; 169 170 @Override 171 public Object create() throws HeapException { 172 final Options options = Options.defaultOptions(); 173 174 if (config.isDefined("connections")) { 175 options.set(OPTION_MAX_CONNECTIONS, config.get("connections").asInteger()); 176 } 177 178 if (config.isDefined("disableReuseConnection")) { 179 options.set(OPTION_REUSE_CONNECTIONS, !config.get("disableReuseConnection").asBoolean()); 180 } 181 182 if (config.isDefined("disableRetries")) { 183 options.set(OPTION_RETRY_REQUESTS, !config.get("disableRetries").asBoolean()); 184 } 185 186 if (config.isDefined("hostnameVerifier")) { 187 options.set(OPTION_HOSTNAME_VERIFIER, config.get("hostnameVerifier") 188 .asEnum(HttpClientHandler.HostnameVerifier.class)); 189 } 190 191 if (config.isDefined("sslContextAlgorithm")) { 192 options.set(OPTION_SSLCONTEXT_ALGORITHM, config.get("sslContextAlgorithm").asString()); 193 } 194 195 if (config.isDefined("soTimeout")) { 196 options.set(OPTION_SO_TIMEOUT, duration(config.get("soTimeout").asString())); 197 } 198 199 if (config.isDefined("connectionTimeout")) { 200 options.set(OPTION_CONNECT_TIMEOUT, duration(config.get("connectionTimeout").asString())); 201 } 202 203 if (config.isDefined("sslEnabledProtocols")) { 204 options.set(OPTION_SSL_ENABLED_PROTOCOLS, config.get("sslEnabledProtocols").asList(String.class)); 205 } 206 207 if (config.isDefined("sslCipherSuites")) { 208 options.set(OPTION_SSL_CIPHER_SUITES, config.get("sslCipherSuites").asList(String.class)); 209 } 210 211 if (config.isDefined("numberOfWorkers")) { 212 options.set(AsyncHttpClientProvider.OPTION_WORKER_THREADS, 213 config.get("numberOfWorkers").asInteger()); 214 } 215 216 options.set(OPTION_TEMPORARY_STORAGE, storage); 217 options.set(OPTION_KEY_MANAGERS, getKeyManagers()); 218 options.set(OPTION_TRUST_MANAGERS, getTrustManagers()); 219 220 try { 221 httpClientHandler = new HttpClientHandler(options); 222 return new ClientHandler(httpClientHandler); 223 } catch (final HttpApplicationException e) { 224 throw new HeapException(format("Cannot build ClientHandler named '%s'", name), e); 225 } 226 } 227 228 @Override 229 public void destroy() { 230 if (httpClientHandler != null) { 231 closeSilently(httpClientHandler); 232 } 233 super.destroy(); 234 } 235 236 private TrustManager[] getTrustManagers() throws HeapException { 237 // Build an optional TrustManagerFactory 238 TrustManager[] trustManagers = null; 239 // Uses TrustManager references 240 if (config.isDefined("trustManager")) { 241 final JsonValue trustManagerConfig = config.get("trustManager"); 242 final List<TrustManager> managers = new ArrayList<>(); 243 if (trustManagerConfig.isList()) { 244 managers.addAll(trustManagerConfig.asList(ofRequiredHeapObject(heap, 245 TrustManager.class))); 246 } else { 247 managers.add(heap.resolve(trustManagerConfig, TrustManager.class)); 248 } 249 trustManagers = managers.toArray(new TrustManager[managers.size()]); 250 } 251 return trustManagers; 252 } 253 254 private KeyManager[] getKeyManagers() throws HeapException { 255 // Build an optional KeyManagerFactory 256 KeyManager[] keyManagers = null; 257 258 // Uses KeyManager references 259 if (config.isDefined("keyManager")) { 260 final JsonValue keyManagerConfig = config.get("keyManager"); 261 final List<KeyManager> managers = new ArrayList<>(); 262 if (keyManagerConfig.isList()) { 263 managers.addAll(keyManagerConfig.asList(ofRequiredHeapObject(heap, 264 KeyManager.class))); 265 } else { 266 managers.add(heap.resolve(keyManagerConfig, KeyManager.class)); 267 } 268 keyManagers = managers.toArray(new KeyManager[managers.size()]); 269 } 270 return keyManagers; 271 } 272 } 273}