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.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         // attempt to redirect the request as many times as the default allows
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         // attempt to redirect the request as fewer times than the default allows
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         // attempt to redirect 1 more time than the default allows
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         // reduce the allowed redirects below the default.
230         System.setProperty(HttpClientParams.MAX_REDIRECTS, "1");
231 
232         // attempt to redirect the default number of times
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         // increase the allowed redirects
243         int allowedRedirects = HttpClientRequest.MAX_REDIRECTS + 1;
244         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
245 
246         // attempt to redirect the default number of times
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         // increase the number of allowed redirects.
257         int allowedRedirects = HttpClientRequest.MAX_REDIRECTS + 1;
258         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
259 
260         // try and redirect up to the new threshold.
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         // increase the number of allowed redirects.
271         int allowedRedirects = HttpClientRequest.MAX_REDIRECTS + 1;
272         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
273 
274         // try and redirect within the new threshold.
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         // 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 within the new threshold.
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         // increase the number of allowed redirects.
299         int allowedRedirects = 0;
300         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
301 
302         // try and redirect above the new threshold.
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         // increase the number of allowed redirects.
313         int allowedRedirects = -1;
314         System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + allowedRedirects);
315 
316         // try and redirect up to the new threshold.
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 }