1 package com.atlassian.sal.core.net;
2
3 import java.io.IOException;
4 import java.net.URL;
5 import java.net.UnknownHostException;
6 import java.security.KeyManagementException;
7 import java.security.NoSuchAlgorithmException;
8 import java.security.cert.X509Certificate;
9 import java.util.Arrays;
10 import java.util.Set;
11
12 import javax.net.ssl.SSLContext;
13 import javax.net.ssl.SSLException;
14 import javax.net.ssl.SSLSocket;
15 import javax.net.ssl.SSLSocketFactory;
16 import javax.net.ssl.TrustManager;
17 import javax.net.ssl.X509TrustManager;
18 import javax.servlet.ServletException;
19 import javax.servlet.http.HttpServletRequest;
20 import javax.servlet.http.HttpServletResponse;
21
22 import com.atlassian.sal.api.net.Request;
23 import com.atlassian.sal.api.net.ResponseException;
24 import com.atlassian.sal.api.net.ResponseHandler;
25 import com.atlassian.sal.api.net.ResponseProtocolException;
26
27 import com.google.common.collect.Sets;
28
29 import org.apache.commons.httpclient.params.HttpClientParams;
30 import org.apache.commons.io.IOUtils;
31 import org.eclipse.jetty.server.Connector;
32 import org.eclipse.jetty.server.Handler;
33 import org.eclipse.jetty.server.Server;
34 import org.eclipse.jetty.server.handler.AbstractHandler;
35 import org.eclipse.jetty.server.ssl.SslSocketConnector;
36 import org.eclipse.jetty.util.ssl.SslContextFactory;
37 import org.junit.After;
38 import org.junit.Before;
39 import org.junit.Rule;
40 import org.junit.Test;
41 import org.junit.rules.ExpectedException;
42
43 import static junit.framework.Assert.assertEquals;
44 import static org.apache.commons.io.IOUtils.write;
45 import static org.hamcrest.Matchers.isA;
46 import static org.junit.Assert.fail;
47
48 public class TestHttpClientRequestRedirects
49 {
50 private HttpTestServer server;
51 private HttpClientRequestFactory requestFactory;
52
53 private SSLContext originalSSLContext;
54
55 @Rule
56 public ExpectedException thrown = ExpectedException.none();
57
58 @Before
59 public void setUp() throws NoSuchAlgorithmException, KeyManagementException
60 {
61 originalSSLContext = SSLContext.getDefault();
62 SSLContext.setDefault(getGullibleSSLContext());
63 requestFactory = new HttpClientRequestFactory(null, null);
64 }
65
66 @After
67 public void stopServer() throws Exception
68 {
69 SSLContext.setDefault(originalSSLContext);
70 System.clearProperty(HttpClientParams.MAX_REDIRECTS);
71 server.stop();
72 }
73
74 @Test
75 public void testHttpClientAccessHttpUrl() throws Exception
76 {
77 server = new HttpTestServer();
78 server.start();
79
80 assertGettingUrlSucceeds("http://localhost:" + server.getHttpPort());
81 }
82
83 @Test
84 public void testHttpClientAccessHttpsWithSSL() throws Exception
85 {
86 server = new SslTestServer();
87 server.start();
88 assertGettingUrlSucceeds("https://localhost:" + server.getHttpPort());
89 }
90
91 @Test
92 public void testHttpClientAccessHttpsWithSSLChecksServerCertificateHostName()
93 throws Exception
94 {
95 server = new SslTestServer();
96 server.start();
97 final String url = "https://127.0.0.1:" + server.getHttpPort();
98
99 thrown.expect(ResponseException.class);
100 thrown.expectCause(isA(SSLException.class));
101
102 requestFactory.createRequest(Request.MethodType.GET, url).execute();
103 }
104
105 @Test (expected = ResponseException.class)
106 public void testHttpClientAccessHttpsFailsToAccessServerWithOnlySSlv3() throws Exception
107 {
108 server = new SslTestServer("SSLv3");
109 server.start();
110
111 assertGettingUrlSucceeds("https://localhost:" + server.getHttpPort());
112 }
113
114 @Test
115 public void testHttpClientAccessHttpsSucceedsToAccessServerWithOnlySSlv3WithSystemProperty() throws Exception
116 {
117 server = new SslTestServer();
118 server.start();
119 if (!isSSLv3EnabledByDefault())
120 {
121 return;
122 }
123 try
124 {
125 System.setProperty("https.protocols", "SSLv3");
126 assertGettingUrlSucceeds("https://localhost:" + server.getHttpPort());
127 }
128 finally
129 {
130 System.clearProperty("https.protocols");
131 }
132 }
133
134 @Test
135 public void testHttpClientAccessHttpsWithTLSv1Only() throws Exception
136 {
137 server = new SslTestServer("TLSv1");
138 server.start();
139
140 assertGettingUrlSucceeds("https://localhost:" + server.getHttpPort());
141 }
142
143 @Test (expected = ResponseProtocolException.class)
144 public void testHttpClientAccessFailsWithTLSv1OnSSLv3() throws Exception
145 {
146 server = new SslTestServer("SSLv3");
147 server.start();
148
149 try
150 {
151 System.setProperty("https.protocols", "TLSv1");
152 HttpClientRequest request = requestFactory.createRequest(Request.MethodType.GET, ("http://localhost:" + server.getHttpPort()));
153 request.execute(checkResponseHandler);
154 }
155 finally
156 {
157 System.clearProperty("https.protocols");
158 }
159 }
160
161 @Test
162 public void testReusingRequestWithAndWithoutSSL() throws Exception
163 {
164 server = new HttpTestServer();
165 server.start();
166
167 HttpClientRequest request = requestFactory.createRequest(Request.MethodType.GET, ("http://localhost:" + server.getHttpPort()));
168 request.execute(checkResponseHandler);
169
170 server.stop();
171 server = new SslTestServer("TLSv1");
172 server.start();
173
174 request.setUrl("https://localhost:" + server.getHttpPort());
175 request.execute(checkResponseHandler);
176 }
177
178 @Test
179 public void testRedirectFromTLSv1() throws Exception
180 {
181 HttpTestServer targetServer = new HttpTestServer();
182 server = new SslTestServer("TLSv1").handler(new RedirectMockHandler(targetServer, "localhost"));
183 server.start();
184 try
185 {
186 targetServer.start();
187
188 assertGettingUrlSucceeds("https://localhost:" + server.getHttpPort());
189 }
190 finally
191 {
192 targetServer.stop();
193 }
194 }
195
196 @Test
197 public void verifyRedirectSucceedsWithRedirectsUptoTheDefaultThreshold() throws Exception
198 {
199
200 server = new RedirectingHttpServer(HttpClientRequest.MAX_REDIRECTS);
201
202 server.start();
203 assertGettingUrlSucceeds("http://localhost:" + server.getHttpPort());
204 }
205
206 @Test
207 public void verifyRedirectSucceedsWithRedirectsWithinTheDefaultThreshold() throws Exception
208 {
209
210 server = new RedirectingHttpServer(HttpClientRequest.MAX_REDIRECTS - 1);
211
212 server.start();
213 assertGettingUrlSucceeds("http://localhost:" + server.getHttpPort());
214 }
215
216 @Test(expected = ResponseProtocolException.class)
217 public void verifyRedirectFailsWithRedirectsMoreThanTheDefaultThreshold() throws Exception
218 {
219
220 server = new RedirectingHttpServer(HttpClientRequest.MAX_REDIRECTS + 1);
221
222 server.start();
223 assertGettingUrlSucceeds("http://localhost:" + server.getHttpPort());
224 }
225
226 @Test(expected = ResponseProtocolException.class)
227 public void verifyRedirectFailsWithRedirectsMoreThanLoweredThreshold() throws Exception
228 {
229
230 System.setProperty(HttpClientParams.MAX_REDIRECTS, "1");
231
232
233 server = new RedirectingHttpServer(HttpClientRequest.MAX_REDIRECTS);
234
235 server.start();
236 assertGettingUrlSucceeds("http://localhost:" + server.getHttpPort());
237 }
238
239 @Test(expected = ResponseProtocolException.class)
240 public void verifyRedirectFailsWithRedirectsMoreThanRaisedThreshold() throws Exception
241 {
242
243 int allowedRedirects = HttpClientRequest.MAX_REDIRECTS + 1;
244 System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
245
246
247 server = new RedirectingHttpServer(allowedRedirects + 1);
248
249 server.start();
250 assertGettingUrlSucceeds("http://localhost:" + server.getHttpPort());
251 }
252
253 @Test
254 public void verifyRedirectSucceedsWithRedirectsUpTpRaisedThreshold() throws Exception
255 {
256
257 int allowedRedirects = HttpClientRequest.MAX_REDIRECTS + 1;
258 System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
259
260
261 server = new RedirectingHttpServer(allowedRedirects);
262
263 server.start();
264 assertGettingUrlSucceeds("http://localhost:" + server.getHttpPort());
265 }
266
267 @Test
268 public void verifyRedirectSucceedsWithRedirectsWithinRaisedThreshold() throws Exception
269 {
270
271 int allowedRedirects = HttpClientRequest.MAX_REDIRECTS + 1;
272 System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
273
274
275 server = new RedirectingHttpServer(allowedRedirects - 1);
276
277 server.start();
278 assertGettingUrlSucceeds("http://localhost:" + server.getHttpPort());
279 }
280
281 @Test
282 public void verifyRedirectSucceedsWithRedirectsWithinLoweredThreshold() throws Exception
283 {
284
285 int allowedRedirects = HttpClientRequest.MAX_REDIRECTS - 1;
286 System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
287
288
289 server = new RedirectingHttpServer(allowedRedirects - 1);
290
291 server.start();
292 assertGettingUrlSucceeds("http://localhost:" + server.getHttpPort());
293 }
294
295 @Test(expected = ResponseProtocolException.class)
296 public void verifyRedirectFailsSensiblyWithZeroThreshold() throws Exception
297 {
298
299 int allowedRedirects = 0;
300 System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
301
302
303 server = new RedirectingHttpServer(1);
304
305 server.start();
306 assertGettingUrlSucceeds("http://localhost:" + server.getHttpPort());
307 }
308
309 @Test(expected = ResponseProtocolException.class)
310 public void verifyRedirectFailsSensiblyWithNegativeThreshold() throws Exception
311 {
312
313 int allowedRedirects = -1;
314 System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
315
316
317 server = new RedirectingHttpServer(2);
318
319 server.start();
320 assertGettingUrlSucceeds("http://localhost:" + server.getHttpPort());
321 }
322
323 private ResponseHandler<HttpClientResponse> checkResponseHandler = new ResponseHandler<HttpClientResponse>()
324 {
325 @Override
326 public void handle(HttpClientResponse response) throws ResponseException
327 {
328 assertEquals(200, response.getStatusCode());
329 try
330 {
331 assertEquals(HttpTestServer.RESPONSE, IOUtils.toString(response.getResponseBodyAsStream(), "UTF-8"));
332 } catch (IOException e)
333 {
334 fail("Failed to read response body: " + e.getMessage());
335 }
336 }
337 };
338
339 private void assertGettingUrlSucceeds(String url) throws Exception
340 {
341 requestFactory.createRequest(Request.MethodType.GET, url).execute(checkResponseHandler);
342 }
343
344 private SSLContext getGullibleSSLContext() throws NoSuchAlgorithmException, KeyManagementException
345 {
346 SSLContext sc = SSLContext.getInstance("TLS");
347 sc.init(null, new TrustManager[] { getGullibleTrustManager() }, null);
348 return sc;
349 }
350
351 private TrustManager getGullibleTrustManager()
352 {
353 return
354 new X509TrustManager()
355 {
356 public X509Certificate[] getAcceptedIssuers()
357 {
358 return null;
359 }
360
361 public void checkClientTrusted(X509Certificate[] certs, String authType)
362 {
363 }
364
365 public void checkServerTrusted(X509Certificate[] certs, String authType)
366 {
367 }
368 };
369 }
370
371 private static boolean isSSLv3EnabledByDefault() throws IOException
372 {
373 SSLSocket sslSocket = (SSLSocket) SSLSocketFactory.getDefault().createSocket();
374 return Arrays.asList(sslSocket.getEnabledProtocols()).contains("SSLv3");
375 }
376
377 private class HttpTestServer
378 {
379 public static final int HTTP_PORT = 0;
380 public static final String RESPONSE = "Response body";
381
382 private Server server;
383 private String responseBody;
384 private Handler handler;
385
386 public void start() throws Exception
387 {
388 configureServer();
389 startServer();
390 }
391
392 private void startServer() throws Exception
393 {
394 server.start();
395 }
396
397 protected void configureServer()
398 {
399 server = new Server(HTTP_PORT);
400 server.setHandler(getHandler());
401 }
402
403 public int getHttpPort()
404 {
405 return server.getConnectors()[0].getLocalPort();
406 }
407
408 public Handler getHandler()
409 {
410 if (handler != null)
411 {
412 return handler;
413 }
414
415 return new AbstractHandler()
416 {
417 @Override
418 public void handle(final String s, final org.eclipse.jetty.server.Request request, final HttpServletRequest httpServletRequest, final HttpServletResponse response)
419 throws IOException, ServletException
420 {
421 setResponseBody(RESPONSE);
422 response.setStatus(200);
423 response.setContentType("text/plain;charset=utf-8");
424 write(getResponseBody(), response.getOutputStream());
425 request.setHandled(true);
426 }
427 };
428 }
429
430 public void stop() throws Exception
431 {
432 server.stop();
433 }
434
435 public void setResponseBody(String responseBody)
436 {
437 this.responseBody = responseBody;
438 }
439
440 public String getResponseBody()
441 {
442 return responseBody;
443 }
444
445 protected Server getServer()
446 {
447 return server;
448 }
449
450 public HttpTestServer handler(Handler handler)
451 {
452 this.handler = handler;
453 return this;
454 }
455 }
456
457 private class RedirectingHttpServer extends HttpTestServer
458 {
459 private final int maxRedirects;
460
461 public RedirectingHttpServer(int maxRedirects)
462 {
463 this.maxRedirects = maxRedirects;
464 }
465 public Handler getHandler()
466 {
467 return new RedirectMockHandler(this, "localhost", maxRedirects);
468 }
469 }
470
471 public class SslTestServer extends HttpTestServer
472 {
473 private static final int HTTPS_PORT = 0;
474
475 private final Set<String> excludedProtocols = Sets.newHashSet("TLSv1", "SSLv3", "SSLv2Hello");
476 private final String includeProtocols;
477
478 private Connector secureConnector;
479
480 public SslTestServer()
481 {
482 includeProtocols = "TLSv1,SSLv3,SSLv2Hello";
483 }
484
485 public SslTestServer(String includeProtocols)
486 {
487 this.includeProtocols = includeProtocols;
488 }
489
490 @Override
491 protected void configureServer()
492 {
493 super.configureServer();
494 secureConnector = createSecureConnector();
495 getServer().addConnector(secureConnector);
496 }
497
498 private Connector createSecureConnector()
499 {
500
501 SslContextFactory sslContextFactory = new SslContextFactory();
502 URL keystoreUrl = getClass().getClassLoader().getResource("keystore");
503 sslContextFactory.setKeyStorePath(keystoreUrl.getFile());
504 sslContextFactory.setKeyStorePassword("changeit");
505 String[] includes = includeProtocols.split(",");
506
507 excludedProtocols.removeAll(Arrays.asList(includes));
508 sslContextFactory.setExcludeProtocols(excludedProtocols.toArray(new String[excludedProtocols.size()]));
509 sslContextFactory.setIncludeProtocols(includes);
510
511 SslSocketConnector connector = new SslSocketConnector(sslContextFactory);
512 connector.setPort(HTTPS_PORT);
513
514 return connector;
515 }
516
517 @Override
518 public int getHttpPort()
519 {
520 return secureConnector.getLocalPort();
521 }
522
523 public Handler getHandler()
524 {
525 final Handler handler = super.getHandler();
526 return new AbstractHandler()
527 {
528 @Override
529 public void handle(final String s, final org.eclipse.jetty.server.Request request, final HttpServletRequest httpServletRequest, final HttpServletResponse response)
530 throws IOException, ServletException
531 {
532 assertEquals("localhost:" + getHttpPort(), httpServletRequest.getHeader("Host"));
533 handler.handle(s, request, httpServletRequest, response);
534 }
535 };
536 }
537 }
538
539 private static class RedirectMockHandler extends AbstractHandler
540 {
541 private final String targetHostname;
542 private final HttpTestServer targetServer;
543 private final int maxRedirects;
544 private int redirectsCount = 0;
545
546 private RedirectMockHandler(HttpTestServer targetServer, String targetHostname) throws UnknownHostException
547 {
548 this(targetServer, targetHostname, 1);
549 }
550
551 private RedirectMockHandler(HttpTestServer targetServer, String targetHostname, int maxRedirects)
552 {
553 this.targetServer = targetServer;
554 this.targetHostname = targetHostname;
555 this.maxRedirects = maxRedirects;
556 }
557
558 @Override
559 public void handle(final String target, final org.eclipse.jetty.server.Request baseRequest, final HttpServletRequest request, final HttpServletResponse response)
560 throws IOException, ServletException
561 {
562 if(redirectsCount >= maxRedirects)
563 {
564 response.setStatus(200);
565 write(HttpTestServer.RESPONSE, response.getOutputStream());
566 }
567 else
568 {
569 redirectsCount++;
570 response.setStatus(301);
571 String scheme = (targetServer instanceof SslTestServer) ? "https://" : "http://";
572 response.setHeader("Location", scheme + targetHostname + ":" + targetServer.getHttpPort());
573 write("Redirect", response.getOutputStream());
574 }
575
576 response.setContentType("text/plain;charset=utf-8");
577 baseRequest.setHandled(true);
578 }
579 }
580 }