View Javadoc

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         // attempt to redirect the request as many times as the default allows
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         // attempt to redirect the request as fewer times than the default allows
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         // attempt to redirect 1 more time than the default allows
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         // reduce the allowed redirects below the default.
258         System.setProperty(HttpClientParams.MAX_REDIRECTS, "1");
259 
260         // attempt to redirect the default number of times
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         // increase the allowed redirects
271         int allowedRedirects = HttpClientRequest.MAX_REDIRECTS + 1;
272         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
273 
274         // attempt to redirect the default number of times
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         // increase the number of allowed redirects.
285         int allowedRedirects = HttpClientRequest.MAX_REDIRECTS + 1;
286         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
287 
288         // try and redirect up to the new threshold.
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         // increase the number of allowed redirects.
299         int allowedRedirects = HttpClientRequest.MAX_REDIRECTS + 1;
300         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
301 
302         // try and redirect within the new threshold.
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         // increase the number of allowed redirects.
313         int allowedRedirects = HttpClientRequest.MAX_REDIRECTS - 1;
314         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
315 
316         // try and redirect within the new threshold.
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         // increase the number of allowed redirects.
327         int allowedRedirects = 0;
328         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
329 
330         // try and redirect above the new threshold.
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         // increase the number of allowed redirects.
341         int allowedRedirects = -1;
342         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
343 
344         // try and redirect up to the new threshold.
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      * Provides all secure socket factories, with a socket that ignores
536      * certificate problems (e.g. self-signed certificate).
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 }