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.AccessController;
7 import java.security.KeyStore;
8 import java.security.PrivilegedAction;
9 import java.security.Provider;
10 import java.security.Security;
11 import java.security.cert.X509Certificate;
12 import java.util.Arrays;
13 import java.util.Set;
14
15 import javax.net.ssl.ManagerFactoryParameters;
16 import javax.net.ssl.TrustManager;
17 import javax.net.ssl.TrustManagerFactorySpi;
18 import javax.net.ssl.X509TrustManager;
19 import javax.servlet.ServletException;
20 import javax.servlet.http.HttpServletRequest;
21 import javax.servlet.http.HttpServletResponse;
22
23 import com.atlassian.sal.api.net.Request;
24 import com.atlassian.sal.api.net.ResponseException;
25 import com.atlassian.sal.api.net.ResponseHandler;
26 import com.atlassian.sal.api.net.ResponseProtocolException;
27
28 import com.google.common.collect.Sets;
29
30 import org.apache.commons.httpclient.params.HttpClientParams;
31 import org.apache.commons.io.IOUtils;
32 import org.eclipse.jetty.server.Connector;
33 import org.eclipse.jetty.server.Handler;
34 import org.eclipse.jetty.server.Server;
35 import org.eclipse.jetty.server.handler.AbstractHandler;
36 import org.eclipse.jetty.server.ssl.SslSocketConnector;
37 import org.eclipse.jetty.util.ssl.SslContextFactory;
38 import org.junit.After;
39 import org.junit.AfterClass;
40 import org.junit.Before;
41 import org.junit.BeforeClass;
42 import org.junit.Test;
43
44 import static junit.framework.Assert.assertEquals;
45 import static org.apache.commons.io.IOUtils.write;
46 import static org.junit.Assert.fail;
47
48 public class TestHttpClientRequestRedirects
49 {
50 private HttpTestServer server;
51 private HttpClientRequestFactory requestFactory;
52
53 @BeforeClass
54 public static void installTrustProvider()
55 {
56 GullibleTrustProvider.install();
57 }
58
59 @AfterClass
60 public static void uninstallTrustProvider()
61 {
62 GullibleTrustProvider.uninstall();
63 }
64
65 @Before
66 public void setUp()
67 {
68 requestFactory = new HttpClientRequestFactory(null, null);
69 }
70
71 @After
72 public void stopServer() throws Exception
73 {
74 System.clearProperty(HttpClientParams.MAX_REDIRECTS);
75 server.stop();
76 }
77
78 @Test
79 public void testHttpClientAccessHttpUrl() throws Exception
80 {
81 server = new HttpTestServer();
82 server.start();
83
84 testGetUrl("http://localhost:" + server.getHttpPort());
85 }
86
87 @Test
88 public void testHttpClientAccessHttpsWithSSL() throws Exception
89 {
90 server = new SslTestServer();
91 server.start();
92
93 testGetUrl("https://localhost:" + server.getHttpPort());
94 }
95
96 @Test
97 public void testHttpClientAccessHttpsWithSSLv3Only() throws Exception
98 {
99 server = new SslTestServer("SSLv3");
100 server.start();
101
102 testGetUrl("https://localhost:" + server.getHttpPort());
103 }
104
105 @Test
106 public void testHttpClientAccessHttpsWithTLSv1Only() throws Exception
107 {
108 server = new SslTestServer("TLSv1");
109 server.start();
110
111 testGetUrl("https://localhost:" + server.getHttpPort());
112 }
113
114 @Test (expected = ResponseProtocolException.class)
115 public void testHttpClientAccessFailsWithSSlv3OnTLSv1() throws Exception
116 {
117 server = new SslTestServer("TLSv1");
118 server.start();
119
120 try
121 {
122 System.setProperty("https.protocols", "SSLv3");
123 HttpClientRequest request = requestFactory.createRequest(Request.MethodType.GET, ("http://localhost:" + server.getHttpPort()));
124 request.execute(checkResponseHandler);
125 }
126 finally
127 {
128 System.clearProperty("https.protocols");
129 }
130 }
131
132 @Test (expected = ResponseProtocolException.class)
133 public void testHttpClientAccessFailsWithTLSv1OnSSLv3() throws Exception
134 {
135 server = new SslTestServer("SSLv3");
136 server.start();
137
138 try
139 {
140 System.setProperty("https.protocols", "TLSv1");
141 HttpClientRequest request = requestFactory.createRequest(Request.MethodType.GET, ("http://localhost:" + server.getHttpPort()));
142 request.execute(checkResponseHandler);
143 }
144 finally
145 {
146 System.clearProperty("https.protocols");
147 }
148 }
149
150 @Test
151 public void testReusingRequestWithAndWithoutSSL() throws Exception
152 {
153 server = new HttpTestServer();
154 server.start();
155
156 HttpClientRequest request = requestFactory.createRequest(Request.MethodType.GET, ("http://localhost:" + server.getHttpPort()));
157 request.execute(checkResponseHandler);
158
159 server.stop();
160 server = new SslTestServer("SSLv3");
161 server.start();
162
163 request.setUrl("https://localhost:" + server.getHttpPort());
164 request.execute(checkResponseHandler);
165 }
166
167 @Test
168 public void testRedirectToSSLv3() throws Exception
169 {
170 HttpTestServer targetServer = new SslTestServer("SSLv3");
171 server = new HttpRedirectTestServer(targetServer, "localhost");
172 server.start();
173
174 try
175 {
176 targetServer.start();
177
178 testGetUrl("http://localhost:" + server.getHttpPort());
179 }
180 finally
181 {
182 targetServer.stop();
183 }
184 }
185
186 @Test
187 public void testRedirectFromSSLv3() throws Exception
188 {
189 HttpTestServer targetServer = new HttpTestServer();
190 server = new SslTestServer("SSLv3").handler(new RedirectMockHandler(targetServer, "localhost"));
191 server.start();
192
193 try
194 {
195 targetServer.start();
196
197 testGetUrl("https://localhost:" + server.getHttpPort());
198 }
199 finally
200 {
201 targetServer.stop();
202 }
203 }
204
205 @Test
206 public void testRedirectFromTLSv1ToSSLv3() throws Exception
207 {
208 HttpTestServer targetServer = new SslTestServer("SSLv3");
209
210 server = new SslTestServer("TLSv1").handler(new RedirectMockHandler(targetServer, "localhost"));
211 server.start();
212 try
213 {
214 targetServer.start();
215
216 testGetUrl("https://localhost:" + server.getHttpPort());
217 }
218 finally
219 {
220 targetServer.stop();
221 }
222 }
223
224 @Test
225 public void verifyRedirectSucceedsWithRedirectsUptoTheDefaultThreshold() throws Exception
226 {
227
228 server = new RedirectingHttpServer(HttpClientRequest.MAX_REDIRECTS);
229
230 server.start();
231 testGetUrl("http://localhost:" + server.getHttpPort());
232 }
233
234 @Test
235 public void verifyRedirectSucceedsWithRedirectsWithinTheDefaultThreshold() throws Exception
236 {
237
238 server = new RedirectingHttpServer(HttpClientRequest.MAX_REDIRECTS - 1);
239
240 server.start();
241 testGetUrl("http://localhost:" + server.getHttpPort());
242 }
243
244 @Test(expected = ResponseProtocolException.class)
245 public void verifyRedirectFailsWithRedirectsMoreThanTheDefaultThreshold() throws Exception
246 {
247
248 server = new RedirectingHttpServer(HttpClientRequest.MAX_REDIRECTS + 1);
249
250 server.start();
251 testGetUrl("http://localhost:" + server.getHttpPort());
252 }
253
254 @Test(expected = ResponseProtocolException.class)
255 public void verifyRedirectFailsWithRedirectsMoreThanLoweredThreshold() throws Exception
256 {
257
258 System.setProperty(HttpClientParams.MAX_REDIRECTS, "1");
259
260
261 server = new RedirectingHttpServer(HttpClientRequest.MAX_REDIRECTS);
262
263 server.start();
264 testGetUrl("http://localhost:" + server.getHttpPort());
265 }
266
267 @Test(expected = ResponseProtocolException.class)
268 public void verifyRedirectFailsWithRedirectsMoreThanRaisedThreshold() 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 testGetUrl("http://localhost:" + server.getHttpPort());
279 }
280
281 @Test
282 public void verifyRedirectSucceedsWithRedirectsUpTpRaisedThreshold() 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);
290
291 server.start();
292 testGetUrl("http://localhost:" + server.getHttpPort());
293 }
294
295 @Test
296 public void verifyRedirectSucceedsWithRedirectsWithinRaisedThreshold() throws Exception
297 {
298
299 int allowedRedirects = HttpClientRequest.MAX_REDIRECTS + 1;
300 System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
301
302
303 server = new RedirectingHttpServer(allowedRedirects - 1);
304
305 server.start();
306 testGetUrl("http://localhost:" + server.getHttpPort());
307 }
308
309 @Test
310 public void verifyRedirectSucceedsWithRedirectsWithinLoweredThreshold() throws Exception
311 {
312
313 int allowedRedirects = HttpClientRequest.MAX_REDIRECTS - 1;
314 System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
315
316
317 server = new RedirectingHttpServer(allowedRedirects - 1);
318
319 server.start();
320 testGetUrl("http://localhost:" + server.getHttpPort());
321 }
322
323 @Test(expected = ResponseProtocolException.class)
324 public void verifyRedirectFailsSensiblyWithZeroThreshold() throws Exception
325 {
326
327 int allowedRedirects = 0;
328 System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
329
330
331 server = new RedirectingHttpServer(1);
332
333 server.start();
334 testGetUrl("http://localhost:" + server.getHttpPort());
335 }
336
337 @Test(expected = ResponseProtocolException.class)
338 public void verifyRedirectFailsSensiblyWithNegativeThreshold() throws Exception
339 {
340
341 int allowedRedirects = -1;
342 System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
343
344
345 server = new RedirectingHttpServer(2);
346
347 server.start();
348 testGetUrl("http://localhost:" + server.getHttpPort());
349 }
350
351 private ResponseHandler<HttpClientResponse> checkResponseHandler = new ResponseHandler<HttpClientResponse>()
352 {
353 @Override
354 public void handle(HttpClientResponse response) throws ResponseException
355 {
356 assertEquals(200, response.getStatusCode());
357 try
358 {
359 assertEquals(HttpTestServer.RESPONSE, IOUtils.toString(response.getResponseBodyAsStream(), "UTF-8"));
360 } catch (IOException e)
361 {
362 fail("Failed to read response body: " + e.getMessage());
363 }
364 }
365 };
366
367 private void testGetUrl(String url) throws Exception
368 {
369 requestFactory.createRequest(Request.MethodType.GET, url).execute(checkResponseHandler);
370 }
371
372 private class HttpTestServer
373 {
374 public static final int HTTP_PORT = 0;
375 public static final String RESPONSE = "Response body";
376
377 private Server server;
378 private String responseBody;
379 private Handler handler;
380
381 public void start() throws Exception
382 {
383 configureServer();
384 startServer();
385 }
386
387 private void startServer() throws Exception
388 {
389 server.start();
390 }
391
392 protected void configureServer()
393 {
394 server = new Server(HTTP_PORT);
395 server.setHandler(getHandler());
396 }
397
398 public int getHttpPort()
399 {
400 return server.getConnectors()[0].getLocalPort();
401 }
402
403 public Handler getHandler()
404 {
405 if (handler != null)
406 {
407 return handler;
408 }
409
410 return new AbstractHandler()
411 {
412 @Override
413 public void handle(final String s, final org.eclipse.jetty.server.Request request, final HttpServletRequest httpServletRequest, final HttpServletResponse response)
414 throws IOException, ServletException
415 {
416 setResponseBody(RESPONSE);
417 response.setStatus(200);
418 response.setContentType("text/plain;charset=utf-8");
419 write(getResponseBody(), response.getOutputStream());
420 request.setHandled(true);
421 }
422 };
423 }
424
425 public void stop() throws Exception
426 {
427 server.stop();
428 }
429
430 public void setResponseBody(String responseBody)
431 {
432 this.responseBody = responseBody;
433 }
434
435 public String getResponseBody()
436 {
437 return responseBody;
438 }
439
440 protected Server getServer()
441 {
442 return server;
443 }
444
445 public HttpTestServer handler(Handler handler)
446 {
447 this.handler = handler;
448 return this;
449 }
450 }
451
452 private class RedirectingHttpServer extends HttpTestServer
453 {
454 private final int maxRedirects;
455
456 public RedirectingHttpServer(int maxRedirects)
457 {
458 this.maxRedirects = maxRedirects;
459 }
460 public Handler getHandler()
461 {
462 return new RedirectMockHandler(this, "localhost", maxRedirects);
463 }
464 }
465
466 public class SslTestServer extends HttpTestServer
467 {
468 private static final int HTTPS_PORT = 0;
469
470 private final Set<String> excludedProtocols = Sets.newHashSet("TLSv1", "SSLv3", "SSLv2Hello");
471 private final String includeProtocols;
472
473 private Connector secureConnector;
474
475 public SslTestServer()
476 {
477 includeProtocols = "TLSv1,SSLv3,SSLv2Hello";
478 }
479
480 public SslTestServer(String includeProtocols)
481 {
482 this.includeProtocols = includeProtocols;
483 }
484
485 @Override
486 protected void configureServer()
487 {
488 super.configureServer();
489 secureConnector = createSecureConnector();
490 getServer().addConnector(secureConnector);
491 }
492
493 private Connector createSecureConnector()
494 {
495
496 SslContextFactory sslContextFactory = new SslContextFactory();
497 URL keystoreUrl = getClass().getClassLoader().getResource("keystore");
498 sslContextFactory.setKeyStorePath(keystoreUrl.getFile());
499 sslContextFactory.setKeyStorePassword("changeit");
500 String[] includes = includeProtocols.split(",");
501
502 excludedProtocols.removeAll(Arrays.asList(includes));
503 sslContextFactory.setExcludeProtocols(excludedProtocols.toArray(new String[excludedProtocols.size()]));
504 sslContextFactory.setIncludeProtocols(includes);
505
506 SslSocketConnector connector = new SslSocketConnector(sslContextFactory);
507 connector.setPort(HTTPS_PORT);
508
509 return connector;
510 }
511
512 @Override
513 public int getHttpPort()
514 {
515 return secureConnector.getLocalPort();
516 }
517
518 public Handler getHandler()
519 {
520 final Handler handler = super.getHandler();
521 return new AbstractHandler()
522 {
523 @Override
524 public void handle(final String s, final org.eclipse.jetty.server.Request request, final HttpServletRequest httpServletRequest, final HttpServletResponse response)
525 throws IOException, ServletException
526 {
527 assertEquals("localhost:" + getHttpPort(), httpServletRequest.getHeader("Host"));
528 handler.handle(s, request, httpServletRequest, response);
529 }
530 };
531 }
532 }
533
534
535
536
537
538 private static final class GullibleTrustProvider extends Provider
539 {
540 private static String previousAlgorithm;
541 private static final String TRUST_PROVIDER_ALGORITHM = "GullibleTrustAlgorithm";
542 private static final String TRUST_PROVIDER_ID = "GullibleTrustProvider";
543
544 protected GullibleTrustProvider()
545 {
546 super(TRUST_PROVIDER_ID, 1.0, "provides all secure socket factories by ignoring problems in the chain of certificate trust");
547
548 AccessController.doPrivileged(new PrivilegedAction()
549 {
550 public Object run()
551 {
552 put("TrustManagerFactory." + GullibleTrustManagerFactory
553 .getAlgorithm(), GullibleTrustManagerFactory.class.getName());
554 return null;
555 }
556 });
557 }
558
559 public static void install()
560 {
561 if (Security.getProvider(TRUST_PROVIDER_ID) == null)
562 {
563 Security.insertProviderAt(new GullibleTrustProvider(), 2);
564 previousAlgorithm = Security.getProperty("ssl.TrustManagerFactory.algorithm");
565 Security.setProperty("ssl.TrustManagerFactory.algorithm", GullibleTrustManagerFactory.getAlgorithm());
566 }
567 }
568
569 public static void uninstall()
570 {
571 if (Security.getProvider(TRUST_PROVIDER_ID) != null)
572 {
573 Security.removeProvider(TRUST_PROVIDER_ID);
574 Security.setProperty("ssl.TrustManagerFactory.algorithm", previousAlgorithm);
575 }
576 }
577
578 public final static class GullibleTrustManagerFactory extends TrustManagerFactorySpi
579 {
580 public GullibleTrustManagerFactory()
581 {
582 }
583
584 protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
585 {
586 }
587
588 protected void engineInit(KeyStore keystore)
589 {
590 }
591
592 protected TrustManager[] engineGetTrustManagers()
593 {
594 return new TrustManager[]{
595 new X509TrustManager()
596 {
597 public X509Certificate[] getAcceptedIssuers()
598 {
599 return null;
600 }
601
602 public void checkClientTrusted(X509Certificate[] certs, String authType)
603 {
604 }
605
606 public void checkServerTrusted(X509Certificate[] certs, String authType)
607 {
608 }
609 }
610 };
611 }
612
613 public static String getAlgorithm()
614 {
615 return TRUST_PROVIDER_ALGORITHM;
616 }
617 }
618 }
619
620 private class HttpRedirectTestServer extends HttpTestServer
621 {
622 private HttpRedirectTestServer(HttpTestServer targetServer, String targetHostname) throws UnknownHostException
623 {
624 handler(new RedirectMockHandler(targetServer, targetHostname));
625 }
626 }
627
628 private static class RedirectMockHandler extends AbstractHandler
629 {
630 private final String targetHostname;
631 private final HttpTestServer targetServer;
632 private final int maxRedirects;
633 private int redirectsCount = 0;
634
635 private RedirectMockHandler(HttpTestServer targetServer, String targetHostname) throws UnknownHostException
636 {
637 this(targetServer, targetHostname, 1);
638 }
639
640 private RedirectMockHandler(HttpTestServer targetServer, String targetHostname, int maxRedirects)
641 {
642 this.targetServer = targetServer;
643 this.targetHostname = targetHostname;
644 this.maxRedirects = maxRedirects;
645 }
646
647 @Override
648 public void handle(final String target, final org.eclipse.jetty.server.Request baseRequest, final HttpServletRequest request, final HttpServletResponse response)
649 throws IOException, ServletException
650 {
651 if(redirectsCount >= maxRedirects)
652 {
653 response.setStatus(200);
654 write(HttpTestServer.RESPONSE, response.getOutputStream());
655 }
656 else
657 {
658 redirectsCount++;
659 response.setStatus(301);
660 String scheme = (targetServer instanceof SslTestServer) ? "https://" : "http://";
661 response.setHeader("Location", scheme + targetHostname + ":" + targetServer.getHttpPort());
662 write("Redirect", response.getOutputStream());
663 }
664
665 response.setContentType("text/plain;charset=utf-8");
666 baseRequest.setHandled(true);
667 }
668 }
669 }