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}