|
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 } |