test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/AuthenticationFilterTest.java
branchhttp-client-branch
changeset 56103 d5f70938e399
child 56126 86e628130926
equal deleted inserted replaced
56102:34f5fa24fdfb 56103:d5f70938e399
       
     1 /*
       
     2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 package jdk.internal.net.http;
       
    25 
       
    26 import jdk.internal.net.http.common.HttpHeadersImpl;
       
    27 import org.testng.annotations.DataProvider;
       
    28 import org.testng.annotations.Test;
       
    29 import org.testng.annotations.AfterClass;
       
    30 
       
    31 import java.lang.ref.Reference;
       
    32 import java.net.Authenticator;
       
    33 import java.net.InetSocketAddress;
       
    34 import java.net.PasswordAuthentication;
       
    35 import java.net.ProxySelector;
       
    36 import java.net.URI;
       
    37 import java.net.URL;
       
    38 import java.net.http.HttpClient;
       
    39 import java.net.http.HttpHeaders;
       
    40 import java.net.http.HttpResponse;
       
    41 import java.security.AccessController;
       
    42 import java.util.Arrays;
       
    43 import java.util.Base64;
       
    44 import java.util.concurrent.ConcurrentHashMap;
       
    45 import java.util.concurrent.ConcurrentMap;
       
    46 import java.util.concurrent.atomic.AtomicLong;
       
    47 import java.net.http.HttpClient.Version;
       
    48 
       
    49 import static java.lang.String.format;
       
    50 import static java.lang.System.out;
       
    51 import static java.nio.charset.StandardCharsets.US_ASCII;
       
    52 import static java.util.stream.Collectors.joining;
       
    53 import static java.net.http.HttpClient.Version.HTTP_1_1;
       
    54 import static java.net.http.HttpClient.Version.HTTP_2;
       
    55 import static java.net.http.HttpClient.Builder.NO_PROXY;
       
    56 import static org.testng.Assert.*;
       
    57 
       
    58 public class AuthenticationFilterTest {
       
    59 
       
    60     @DataProvider(name = "uris")
       
    61     public Object[][] responses() {
       
    62         return new Object[][] {
       
    63                 { "http://foo.com", HTTP_1_1, null },
       
    64                 { "http://foo.com", HTTP_2, null },
       
    65                 { "http://foo.com#blah", HTTP_1_1, null },
       
    66                 { "http://foo.com#blah", HTTP_2, null },
       
    67                 { "http://foo.com/x/y/z", HTTP_1_1, null },
       
    68                 { "http://foo.com/x/y/z", HTTP_2, null },
       
    69                 { "http://foo.com/x/y/z#blah", HTTP_1_1, null },
       
    70                 { "http://foo.com/x/y/z#blah", HTTP_2, null },
       
    71                 { "http://foo.com:80", HTTP_1_1, null },
       
    72                 { "http://foo.com:80", HTTP_2, null },
       
    73                 { "http://foo.com:80#blah", HTTP_1_1, null },
       
    74                 { "http://foo.com:80#blah", HTTP_2, null },
       
    75                 { "http://foo.com", HTTP_1_1, "127.0.0.1:8080" },
       
    76                 { "http://foo.com", HTTP_2, "127.0.0.1:8080" },
       
    77                 { "http://foo.com#blah", HTTP_1_1, "127.0.0.1:8080" },
       
    78                 { "http://foo.com#blah", HTTP_2, "127.0.0.1:8080" },
       
    79                 { "http://foo.com:8080", HTTP_1_1, "127.0.0.1:8080" },
       
    80                 { "http://foo.com:8080", HTTP_2, "127.0.0.1:8080" },
       
    81                 { "http://foo.com:8080#blah", HTTP_1_1, "127.0.0.1:8080" },
       
    82                 { "http://foo.com:8080#blah", HTTP_2, "127.0.0.1:8080" },
       
    83                 { "https://foo.com", HTTP_1_1, null },
       
    84                 { "https://foo.com", HTTP_2, null },
       
    85                 { "https://foo.com#blah", HTTP_1_1, null },
       
    86                 { "https://foo.com#blah", HTTP_2, null },
       
    87                 { "https://foo.com:443", HTTP_1_1, null },
       
    88                 { "https://foo.com:443", HTTP_2, null },
       
    89                 { "https://foo.com:443#blah", HTTP_1_1, null },
       
    90                 { "https://foo.com:443#blah", HTTP_2, null },
       
    91                 { "https://foo.com", HTTP_1_1, "127.0.0.1:8080" },
       
    92                 { "https://foo.com", HTTP_2, "127.0.0.1:8080" },
       
    93                 { "https://foo.com#blah", HTTP_1_1, "127.0.0.1:8080" },
       
    94                 { "https://foo.com#blah", HTTP_2, "127.0.0.1:8080" },
       
    95                 { "https://foo.com:8080", HTTP_1_1, "127.0.0.1:8080" },
       
    96                 { "https://foo.com:8080", HTTP_2, "127.0.0.1:8080" },
       
    97                 { "https://foo.com:8080#blah", HTTP_1_1, "127.0.0.1:8080" },
       
    98                 { "https://foo.com:8080#blah", HTTP_2, "127.0.0.1:8080" },
       
    99                 { "http://foo.com:80/x/y/z", HTTP_1_1, null },
       
   100                 { "http://foo.com:80/x/y/z", HTTP_2, null },
       
   101                 { "http://foo.com:80/x/y/z#blah", HTTP_1_1, null },
       
   102                 { "http://foo.com:80/x/y/z#blah", HTTP_2, null },
       
   103                 { "http://foo.com/x/y/z", HTTP_1_1, "127.0.0.1:8080" },
       
   104                 { "http://foo.com/x/y/z", HTTP_2, "127.0.0.1:8080" },
       
   105                 { "http://foo.com/x/y/z#blah", HTTP_1_1, "127.0.0.1:8080" },
       
   106                 { "http://foo.com/x/y/z#blah", HTTP_2, "127.0.0.1:8080" },
       
   107                 { "http://foo.com:8080/x/y/z", HTTP_1_1, "127.0.0.1:8080" },
       
   108                 { "http://foo.com:8080/x/y/z", HTTP_2, "127.0.0.1:8080" },
       
   109                 { "http://foo.com:8080/x/y/z#blah", HTTP_1_1, "127.0.0.1:8080" },
       
   110                 { "http://foo.com:8080/x/y/z#blah", HTTP_2, "127.0.0.1:8080" },
       
   111                 { "https://foo.com/x/y/z", HTTP_1_1, null },
       
   112                 { "https://foo.com/x/y/z", HTTP_2, null },
       
   113                 { "https://foo.com/x/y/z#blah", HTTP_1_1, null },
       
   114                 { "https://foo.com/x/y/z#blah", HTTP_2, null },
       
   115                 { "https://foo.com:443/x/y/z", HTTP_1_1, null },
       
   116                 { "https://foo.com:443/x/y/z", HTTP_2, null },
       
   117                 { "https://foo.com:443/x/y/z#blah", HTTP_1_1, null },
       
   118                 { "https://foo.com:443/x/y/z#blah", HTTP_2, null },
       
   119                 { "https://foo.com/x/y/z", HTTP_1_1, "127.0.0.1:8080" },
       
   120                 { "https://foo.com/x/y/z", HTTP_2, "127.0.0.1:8080" },
       
   121                 { "https://foo.com/x/y/z#blah", HTTP_1_1, "127.0.0.1:8080" },
       
   122                 { "https://foo.com/x/y/z#blah", HTTP_2, "127.0.0.1:8080" },
       
   123                 { "https://foo.com:8080/x/y/z", HTTP_1_1, "127.0.0.1:8080" },
       
   124                 { "https://foo.com:8080/x/y/z", HTTP_2, "127.0.0.1:8080" },
       
   125                 { "https://foo.com:8080/x/y/z#blah", HTTP_1_1, "127.0.0.1:8080" },
       
   126                 { "https://foo.com:8080/x/y/z#blah", HTTP_2, "127.0.0.1:8080" },
       
   127         };
       
   128     }
       
   129 
       
   130     static final ConcurrentMap<String,Throwable> FAILED = new ConcurrentHashMap<>();
       
   131 
       
   132     static boolean isNullOrEmpty(String s) {
       
   133         return s == null || s.isEmpty();
       
   134     }
       
   135 
       
   136     @Test(dataProvider = "uris")
       
   137     public void testAuthentication(String uri, Version v, String proxy) throws Exception {
       
   138         String test = format("testAuthentication: {\"%s\", %s, \"%s\"}", uri, v, proxy);
       
   139         try {
       
   140             doTestAuthentication(uri, v, proxy);
       
   141         } catch(Exception | Error x) {
       
   142             FAILED.putIfAbsent(test, x);
       
   143             throw x;
       
   144         }
       
   145     }
       
   146 
       
   147     @AfterClass
       
   148     public void printDiagnostic() {
       
   149         if (FAILED.isEmpty()) {
       
   150             out.println("All tests passed");
       
   151             return;
       
   152         }
       
   153         // make sure failures don't disappear in the overflow
       
   154         out.println("Failed tests: ");
       
   155         FAILED.keySet().forEach(s ->
       
   156                 out.println("\t " + s.substring(s.indexOf(':')+1) + ","));
       
   157         out.println();
       
   158         FAILED.entrySet().forEach(e -> {
       
   159                 System.err.println("\n" + e.getKey()
       
   160                         + " FAILED: " + e.getValue());
       
   161                 e.getValue().printStackTrace();
       
   162         });
       
   163     }
       
   164 
       
   165     private void doTestAuthentication(String uri, Version v, String proxy) throws Exception {
       
   166         int colon = proxy == null ? -1 : proxy.lastIndexOf(":");
       
   167         ProxySelector ps = proxy == null ? NO_PROXY
       
   168                 : ProxySelector.of(InetSocketAddress.createUnresolved(
       
   169                         proxy.substring(0, colon),
       
   170                         Integer.parseInt(proxy.substring(colon+1))));
       
   171         int unauthorized = proxy == null ? 401 : 407;
       
   172 
       
   173         TestAuthenticator authenticator = new TestAuthenticator();
       
   174 
       
   175         // Creates a HttpClientImpl
       
   176         HttpClientBuilderImpl clientBuilder = new HttpClientBuilderImpl()
       
   177                 .authenticator(authenticator).proxy(ps);
       
   178         HttpClientFacade facade = HttpClientImpl.create(clientBuilder);
       
   179         HttpClientImpl client = facade.impl;
       
   180         AuthenticationFilter filter = new AuthenticationFilter();
       
   181 
       
   182         assertEquals(authenticator.COUNTER.get(), 0);
       
   183 
       
   184         // Creates the first HttpRequestImpl, and call filter.request() with
       
   185         // it. The expectation is that the filter will not add any credentials,
       
   186         // because the cache is empty and we don't know which auth schemes the
       
   187         // server supports yet.
       
   188         URI reqURI = URI.create(uri);
       
   189         HttpRequestBuilderImpl reqBuilder =
       
   190                 new HttpRequestBuilderImpl(reqURI);
       
   191         HttpRequestImpl origReq = new HttpRequestImpl(reqBuilder);
       
   192         HttpRequestImpl req = new HttpRequestImpl(origReq, ps, AccessController.getContext());
       
   193         MultiExchange<?> multi = new MultiExchange<Void>(origReq, req, client,
       
   194                 HttpResponse.BodyHandler.replace(null),
       
   195                 null, AccessController.getContext());
       
   196         Exchange<?> exchange = new Exchange<>(req, multi);
       
   197         out.println("\nSimulating unauthenticated request to " + uri);
       
   198         filter.request(req, multi);
       
   199         assertFalse(req.getSystemHeaders().firstValue(authorization(true)).isPresent());
       
   200         assertFalse(req.getSystemHeaders().firstValue(authorization(false)).isPresent());
       
   201         assertEquals(authenticator.COUNTER.get(), 0);
       
   202 
       
   203         // Creates the Response to the first request, and call filter.response
       
   204         // with it. That response has a 401 or 407 status code.
       
   205         // The expectation is that the filter will return a new request containing
       
   206         // credentials, and will also cache the credentials in the multi exchange.
       
   207         // The credentials shouldn't be put in the cache until the 200 response
       
   208         // for that request arrives.
       
   209         HttpHeadersImpl headers = new HttpHeadersImpl();
       
   210         headers.addHeader(authenticate(proxy!=null),
       
   211                 "Basic realm=\"earth\"");
       
   212         Response response = new Response(req, exchange, headers, unauthorized, v);
       
   213         out.println("Simulating " + unauthorized
       
   214                 + " response from " + uri);
       
   215         HttpRequestImpl next = filter.response(response);
       
   216 
       
   217         out.println("Checking filter's response to "
       
   218                 + unauthorized + " from " + uri);
       
   219         assertTrue(next != null, "next should not be null");
       
   220         String[] up = check(reqURI, next.getSystemHeaders(), proxy);
       
   221         assertEquals(authenticator.COUNTER.get(), 1);
       
   222 
       
   223         // Now simulate a new successful exchange to get the credentials in the cache
       
   224         // We first call filter.request with the request that was previously
       
   225         // returned by the filter, then create a new Response with a 200 status
       
   226         // code, and feed that to the filter with filter.response.
       
   227         // At this point, the credentials will be added to the cache.
       
   228         out.println("Simulating next request with credentials to " + uri);
       
   229         exchange = new Exchange<>(next, multi);
       
   230         filter.request(next, multi);
       
   231         out.println("Checking credentials in request header after filter for " + uri);
       
   232         check(reqURI, next.getSystemHeaders(), proxy);
       
   233         check(next.uri(), next.getSystemHeaders(), proxy);
       
   234         out.println("Simulating  successful response 200 from " + uri);
       
   235         response = new Response(next, exchange, new HttpHeadersImpl(), 200, v);
       
   236         next = filter.response(response);
       
   237         assertTrue(next == null, "next should be null");
       
   238         assertEquals(authenticator.COUNTER.get(), 1);
       
   239 
       
   240         // Now verify that the cache is used for the next request to the same server.
       
   241         // We're going to create a request to the same server by appending "/bar" to
       
   242         // the original request path. Then we're going to feed that to filter.request
       
   243         // The expectation is that filter.request will add the credentials to the
       
   244         // request system headers, because it should find them in the cache.
       
   245         int fragmentIndex = uri.indexOf('#');
       
   246         String subpath = "/bar";
       
   247         String prefix = uri;
       
   248         String fragment =  "";
       
   249         if (fragmentIndex > -1) {
       
   250             prefix = uri.substring(0, fragmentIndex);
       
   251             fragment = uri.substring(fragmentIndex);
       
   252         }
       
   253         URI reqURI2 = URI.create(prefix + subpath + fragment);
       
   254         out.println("Simulating new request to " + reqURI2);
       
   255         HttpRequestBuilderImpl reqBuilder2 =
       
   256                 new HttpRequestBuilderImpl(reqURI2);
       
   257         HttpRequestImpl origReq2 = new HttpRequestImpl(reqBuilder2);
       
   258         HttpRequestImpl req2 = new HttpRequestImpl(origReq2, ps, AccessController.getContext());
       
   259         MultiExchange<?> multi2 = new MultiExchange<Void>(origReq2, req2, client,
       
   260                 HttpResponse.BodyHandler.replace(null),
       
   261                 null, AccessController.getContext());
       
   262         filter.request(req2, multi2);
       
   263         out.println("Check that filter has added credentials from cache for " + reqURI2
       
   264                 + " with proxy " + req2.proxy());
       
   265         String[] up2 = check(reqURI, req2.getSystemHeaders(), proxy);
       
   266         assertTrue(Arrays.deepEquals(up, up2), format("%s:%s != %s:%s", up2[0], up2[1], up[0], up[1]));
       
   267         assertEquals(authenticator.COUNTER.get(), 1);
       
   268 
       
   269         // Now verify that the cache is not used if we send a request to a different server.
       
   270         // We're going to append ".bar" to the original request host name, and feed that
       
   271         // to filter.request.
       
   272         // There are actually two cases: if we were using a proxy, then the new request
       
   273         // should contain proxy credentials. If we were not using a proxy, then it should
       
   274         // not contain any credentials at all.
       
   275         URI reqURI3;
       
   276         if (isNullOrEmpty(reqURI.getPath())
       
   277                 && isNullOrEmpty(reqURI.getFragment())
       
   278                 && reqURI.getPort() == -1) {
       
   279             reqURI3 = URI.create(uri + ".bar");
       
   280         } else {
       
   281             reqURI3 = new URI(reqURI.getScheme(), reqURI.getUserInfo(),
       
   282                          reqURI.getHost() + ".bar", reqURI.getPort(),
       
   283                               reqURI.getPath(), reqURI.getQuery(),
       
   284                               reqURI.getFragment());
       
   285         }
       
   286         out.println("Simulating new request to " + reqURI3);
       
   287         HttpRequestBuilderImpl reqBuilder3 =
       
   288                 new HttpRequestBuilderImpl(reqURI3);
       
   289         HttpRequestImpl origReq3 = new HttpRequestImpl(reqBuilder3);
       
   290         HttpRequestImpl req3 = new HttpRequestImpl(origReq3, ps, AccessController.getContext());
       
   291         MultiExchange<?> multi3 = new MultiExchange<Void>(origReq3, req3, client,
       
   292                 HttpResponse.BodyHandler.replace(null),
       
   293                 null, AccessController.getContext());
       
   294         filter.request(req3, multi3);
       
   295         if (proxy == null) {
       
   296             out.println("Check that filter has not added proxy credentials from cache for " + reqURI3);
       
   297             assert !req3.getSystemHeaders().firstValue(authorization(true)).isPresent()
       
   298                     : format("Unexpected proxy credentials found: %s",
       
   299                     java.util.stream.Stream.of(getAuthorization(req3.getSystemHeaders(), true))
       
   300                             .collect(joining(":")));
       
   301             assertFalse(req3.getSystemHeaders().firstValue(authorization(true)).isPresent());
       
   302         } else {
       
   303             out.println("Check that filter has added proxy credentials from cache for " + reqURI3);
       
   304             String[] up3 = check(reqURI, req3.getSystemHeaders(), proxy);
       
   305             assertTrue(Arrays.deepEquals(up, up3), format("%s:%s != %s:%s", up3[0], up3[1], up[0], up[1]));
       
   306         }
       
   307         out.println("Check that filter has not added server credentials from cache for " + reqURI3);
       
   308         assert !req3.getSystemHeaders().firstValue(authorization(false)).isPresent()
       
   309                 : format("Unexpected server credentials found: %s",
       
   310                 java.util.stream.Stream.of(getAuthorization(req3.getSystemHeaders(), false))
       
   311                         .collect(joining(":")));
       
   312         assertFalse(req3.getSystemHeaders().firstValue(authorization(false)).isPresent());
       
   313         assertEquals(authenticator.COUNTER.get(), 1);
       
   314 
       
   315         // Now we will verify that credentials for proxies are not used for servers and
       
   316         // conversely.
       
   317         // If we were using a proxy, we're now going to send a request to the proxy host,
       
   318         // without using a proxy, and verify that filter.request neither add proxy credential
       
   319         // or server credential to that host.
       
   320         // I we were not using a proxy, we're going to send a request to the original
       
   321         // server, using a proxy whose address matches the original server.
       
   322         // We expect that the cache will add server credentials, but not proxy credentials.
       
   323         int port = reqURI.getPort();
       
   324         port = port == -1 ? defaultPort(reqURI.getScheme()) : port;
       
   325         ProxySelector fakeProxy = proxy == null
       
   326                 ? ProxySelector.of(InetSocketAddress.createUnresolved(
       
   327                 reqURI.getHost(), port))
       
   328                 : NO_PROXY;
       
   329         URI reqURI4 = proxy == null ? reqURI : new URI("http", null, req.proxy().getHostName(),
       
   330                     req.proxy().getPort(), "/", null, null);
       
   331         HttpRequestBuilderImpl reqBuilder4 = new HttpRequestBuilderImpl(reqURI4);
       
   332         HttpRequestImpl origReq4 = new HttpRequestImpl(reqBuilder4);
       
   333         HttpRequestImpl req4 = new HttpRequestImpl(origReq4, fakeProxy,
       
   334                 AccessController.getContext());
       
   335         MultiExchange<?> multi4 = new MultiExchange<Void>(origReq4, req4, client,
       
   336                 HttpResponse.BodyHandler.replace(null), null,
       
   337                 AccessController.getContext());
       
   338         out.println("Simulating new request to " + reqURI4 + " with a proxy " + req4.proxy());
       
   339         assertTrue((req4.proxy() == null) == (proxy != null),
       
   340                 "(req4.proxy() == null) == (proxy != null) should be true");
       
   341         filter.request(req4, multi4);
       
   342         out.println("Check that filter has not added proxy credentials from cache for "
       
   343                 + reqURI4 + " (proxy: " + req4.proxy()  + ")");
       
   344         assert !req4.getSystemHeaders().firstValue(authorization(true)).isPresent()
       
   345                 : format("Unexpected proxy credentials found: %s",
       
   346                 java.util.stream.Stream.of(getAuthorization(req4.getSystemHeaders(), true))
       
   347                         .collect(joining(":")));
       
   348         assertFalse(req4.getSystemHeaders().firstValue(authorization(true)).isPresent());
       
   349         if (proxy != null) {
       
   350             out.println("Check that filter has not added server credentials from cache for "
       
   351                     + reqURI4 + " (proxy: " + req4.proxy()  + ")");
       
   352             assert !req4.getSystemHeaders().firstValue(authorization(false)).isPresent()
       
   353                     : format("Unexpected server credentials found: %s",
       
   354                     java.util.stream.Stream.of(getAuthorization(req4.getSystemHeaders(), false))
       
   355                             .collect(joining(":")));
       
   356             assertFalse(req4.getSystemHeaders().firstValue(authorization(false)).isPresent());
       
   357         } else {
       
   358             out.println("Check that filter has added server credentials from cache for "
       
   359                     + reqURI4 + " (proxy: " + req4.proxy()  + ")");
       
   360             String[] up4 = check(reqURI, req4.getSystemHeaders(), proxy);
       
   361             assertTrue(Arrays.deepEquals(up, up4),  format("%s:%s != %s:%s", up4[0], up4[1], up[0], up[1]));
       
   362         }
       
   363         assertEquals(authenticator.COUNTER.get(), 1);
       
   364 
       
   365         if (proxy != null) {
       
   366             // Now if we were using a proxy, we're going to send the same request than
       
   367             // the original request, but without a proxy, and verify that this time
       
   368             // the cache does not add any server or proxy credential. It should not
       
   369             // add server credential because it should not have them (we only used
       
   370             // proxy authentication so far) and it should not add proxy credentials
       
   371             // because the request has no proxy.
       
   372             HttpRequestBuilderImpl reqBuilder5 = new HttpRequestBuilderImpl(reqURI);
       
   373             HttpRequestImpl origReq5 = new HttpRequestImpl(reqBuilder5);
       
   374             HttpRequestImpl req5 = new HttpRequestImpl(origReq5, NO_PROXY,
       
   375                     AccessController.getContext());
       
   376             MultiExchange<?> multi5 = new MultiExchange<Void>(origReq5, req5, client,
       
   377                     HttpResponse.BodyHandler.replace(null), null,
       
   378                     AccessController.getContext());
       
   379             out.println("Simulating new request to " + reqURI + " with a proxy " + req5.proxy());
       
   380             assertTrue(req5.proxy() == null, "req5.proxy() should be null");
       
   381             Exchange<?> exchange5 = new Exchange<>(req5, multi5);
       
   382             filter.request(req5, multi5);
       
   383             out.println("Check that filter has not added server credentials from cache for "
       
   384                     + reqURI + " (proxy: " + req5.proxy()  + ")");
       
   385             assert !req5.getSystemHeaders().firstValue(authorization(false)).isPresent()
       
   386                     : format("Unexpected server credentials found: %s",
       
   387                     java.util.stream.Stream.of(getAuthorization(req5.getSystemHeaders(), false))
       
   388                             .collect(joining(":")));
       
   389             assertFalse(req5.getSystemHeaders().firstValue(authorization(false)).isPresent());
       
   390             out.println("Check that filter has not added proxy credentials from cache for "
       
   391                     + reqURI + " (proxy: " + req5.proxy()  + ")");
       
   392             assert !req5.getSystemHeaders().firstValue(authorization(true)).isPresent()
       
   393                     : format("Unexpected proxy credentials found: %s",
       
   394                     java.util.stream.Stream.of(getAuthorization(req5.getSystemHeaders(), true))
       
   395                             .collect(joining(":")));
       
   396             assertFalse(req5.getSystemHeaders().firstValue(authorization(true)).isPresent());
       
   397             assertEquals(authenticator.COUNTER.get(), 1);
       
   398 
       
   399             // Now simulate a 401 response from the server
       
   400             HttpHeadersImpl headers5 = new HttpHeadersImpl();
       
   401             headers5.addHeader(authenticate(false),
       
   402                     "Basic realm=\"earth\"");
       
   403             unauthorized = 401;
       
   404             Response response5 = new Response(req5, exchange5, headers5, unauthorized, v);
       
   405             out.println("Simulating " + unauthorized
       
   406                     + " response from " + uri);
       
   407             HttpRequestImpl next5 = filter.response(response5);
       
   408             assertEquals(authenticator.COUNTER.get(), 2);
       
   409 
       
   410             out.println("Checking filter's response to "
       
   411                     + unauthorized + " from " + uri);
       
   412             assertTrue(next5 != null, "next5 should not be null");
       
   413             String[] up5 = check(reqURI, next5.getSystemHeaders(), null);
       
   414 
       
   415             // now simulate a 200 response from the server
       
   416             exchange5 = new Exchange<>(next5, multi5);
       
   417             filter.request(next5, multi5);
       
   418             response5 = new Response(next5, exchange5, new HttpHeadersImpl(), 200, v);
       
   419             filter.response(response5);
       
   420             assertEquals(authenticator.COUNTER.get(), 2);
       
   421 
       
   422             // now send the request again, with proxy this time, and it should have both
       
   423             // server auth and proxy auth
       
   424             HttpRequestBuilderImpl reqBuilder6 = new HttpRequestBuilderImpl(reqURI);
       
   425             HttpRequestImpl origReq6 = new HttpRequestImpl(reqBuilder6);
       
   426             HttpRequestImpl req6 = new HttpRequestImpl(origReq6, ps,
       
   427                     AccessController.getContext());
       
   428             MultiExchange<?> multi6 = new MultiExchange<Void>(origReq6, req6, client,
       
   429                     HttpResponse.BodyHandler.replace(null), null,
       
   430                     AccessController.getContext());
       
   431             out.println("Simulating new request to " + reqURI + " with a proxy " + req6.proxy());
       
   432             assertTrue(req6.proxy() != null, "req6.proxy() should not be null");
       
   433             Exchange<?> exchange6 = new Exchange<>(req6, multi6);
       
   434             filter.request(req6, multi6);
       
   435             out.println("Check that filter has added server credentials from cache for "
       
   436                     + reqURI + " (proxy: " + req6.proxy()  + ")");
       
   437             check(reqURI, req6.getSystemHeaders(), null);
       
   438             out.println("Check that filter has added proxy credentials from cache for "
       
   439                     + reqURI + " (proxy: " + req6.proxy()  + ")");
       
   440             String[] up6 = check(reqURI, req6.getSystemHeaders(), proxy);
       
   441             assertTrue(Arrays.deepEquals(up, up6), format("%s:%s != %s:%s", up6[0], up6[1], up[0], up[1]));
       
   442             assertEquals(authenticator.COUNTER.get(), 2);
       
   443         }
       
   444 
       
   445         if (proxy == null && uri.contains("x/y/z")) {
       
   446             URI reqURI7 = URI.create(prefix + "/../../w/z" + fragment);
       
   447             assertTrue(reqURI7.getPath().contains("../../"));
       
   448             HttpRequestBuilderImpl reqBuilder7 = new HttpRequestBuilderImpl(reqURI7);
       
   449             HttpRequestImpl origReq7 = new HttpRequestImpl(reqBuilder7);
       
   450             HttpRequestImpl req7 = new HttpRequestImpl(origReq7, ps,
       
   451                     AccessController.getContext());
       
   452             MultiExchange<?> multi7 = new MultiExchange<Void>(origReq7, req7, client,
       
   453                     HttpResponse.BodyHandler.replace(null), null,
       
   454                     AccessController.getContext());
       
   455             out.println("Simulating new request to " + reqURI7 + " with a proxy " + req7.proxy());
       
   456             assertTrue(req7.proxy() == null, "req7.proxy() should be null");
       
   457             Exchange<?> exchange7 = new Exchange<>(req7, multi7);
       
   458             filter.request(req7, multi7);
       
   459             out.println("Check that filter has not added server credentials from cache for "
       
   460                     + reqURI7 + " (proxy: " + req7.proxy()  + ") [resolved uri: "
       
   461                     + reqURI7.resolve(".") + " should not match " + reqURI.resolve(".") + "]");
       
   462             assert !req7.getSystemHeaders().firstValue(authorization(false)).isPresent()
       
   463                     : format("Unexpected server credentials found: %s",
       
   464                     java.util.stream.Stream.of(getAuthorization(req7.getSystemHeaders(), false))
       
   465                             .collect(joining(":")));
       
   466             assertFalse(req7.getSystemHeaders().firstValue(authorization(false)).isPresent());
       
   467             out.println("Check that filter has not added proxy credentials from cache for "
       
   468                     + reqURI7 + " (proxy: " + req7.proxy()  + ")");
       
   469             assert !req7.getSystemHeaders().firstValue(authorization(true)).isPresent()
       
   470                     : format("Unexpected proxy credentials found: %s",
       
   471                     java.util.stream.Stream.of(getAuthorization(req7.getSystemHeaders(), true))
       
   472                             .collect(joining(":")));
       
   473             assertFalse(req7.getSystemHeaders().firstValue(authorization(true)).isPresent());
       
   474             assertEquals(authenticator.COUNTER.get(), 1);
       
   475 
       
   476         }
       
   477 
       
   478         Reference.reachabilityFence(facade);
       
   479     }
       
   480 
       
   481     static int defaultPort(String protocol) {
       
   482         if ("http".equalsIgnoreCase(protocol)) return 80;
       
   483         if ("https".equalsIgnoreCase(protocol)) return 443;
       
   484         return -1;
       
   485     }
       
   486 
       
   487     static String authenticate(boolean proxy) {
       
   488         return proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
       
   489     }
       
   490     static String authorization(boolean proxy) {
       
   491         return proxy ? "Proxy-Authorization" : "Authorization";
       
   492     }
       
   493 
       
   494     static String[] getAuthorization(HttpHeaders headers, boolean proxy) {
       
   495         String auth = headers.firstValue(authorization(proxy)).get().substring(6);
       
   496         String pw = new String(Base64.getDecoder().decode(auth), US_ASCII);
       
   497         String[] up = pw.split(":");
       
   498         up[1] = new String(Base64.getDecoder().decode(up[1]), US_ASCII);
       
   499         return up;
       
   500     }
       
   501 
       
   502     static Authenticator.RequestorType requestorType(boolean proxy) {
       
   503         return proxy ? Authenticator.RequestorType.PROXY
       
   504                      : Authenticator.RequestorType.SERVER;
       
   505     }
       
   506 
       
   507     static String[] check(URI reqURI, HttpHeaders headers, String proxy) throws Exception {
       
   508         out.println("Next request headers: " + headers.map());
       
   509         String[] up = getAuthorization(headers, proxy != null);
       
   510         String u = up[0];
       
   511         String p = up[1];
       
   512         out.println("user:password: " + u + ":" + p);
       
   513         String protocol = proxy != null ? "http" : reqURI.getScheme();
       
   514         String expectedUser = "u." + protocol;
       
   515         assertEquals(u, expectedUser);
       
   516         String host = proxy == null ? reqURI.getHost() :
       
   517                 proxy.substring(0, proxy.lastIndexOf(':'));
       
   518         int port = proxy == null ? reqURI.getPort()
       
   519                 : Integer.parseInt(proxy.substring(proxy.lastIndexOf(':')+1));
       
   520         String expectedPw = concat(requestorType(proxy!=null),
       
   521                 "basic", protocol, host,
       
   522                 port, "earth", reqURI.toURL());
       
   523         assertEquals(p, expectedPw);
       
   524         return new String[] {u, p};
       
   525     }
       
   526 
       
   527     static String concat(Authenticator.RequestorType reqType,
       
   528                            String authScheme,
       
   529                            String requestingProtocol,
       
   530                            String requestingHost,
       
   531                            int requestingPort,
       
   532                            String realm,
       
   533                            URL requestingURL) {
       
   534         return new StringBuilder()
       
   535                 .append(reqType).append(":")
       
   536                 .append(authScheme).append(":")
       
   537                 .append(String.valueOf(realm))
       
   538                 .append("[")
       
   539                 .append(requestingProtocol).append(':')
       
   540                 .append(requestingHost).append(':')
       
   541                 .append(requestingPort).append("]")
       
   542                 .append("/").append(String.valueOf(requestingURL))
       
   543                 .toString();
       
   544     }
       
   545 
       
   546     static class TestAuthenticator extends Authenticator {
       
   547         final AtomicLong COUNTER = new AtomicLong();
       
   548         @Override
       
   549         public PasswordAuthentication getPasswordAuthentication() {
       
   550             COUNTER.incrementAndGet();
       
   551             return new PasswordAuthentication("u."+getRequestingProtocol(),
       
   552                     Base64.getEncoder().encodeToString(concat().getBytes(US_ASCII))
       
   553                             .toCharArray());
       
   554         }
       
   555 
       
   556         String concat() {
       
   557             return AuthenticationFilterTest.concat(
       
   558                     getRequestorType(),
       
   559                     getRequestingScheme(),
       
   560                     getRequestingProtocol(),
       
   561                     getRequestingHost(),
       
   562                     getRequestingPort(),
       
   563                     getRequestingPrompt(),
       
   564                     getRequestingURL());
       
   565         }
       
   566 
       
   567     }
       
   568 }