1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.client5.testing.async;
28
29 import static org.hamcrest.MatcherAssert.assertThat;
30
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.Queue;
34 import java.util.concurrent.ConcurrentLinkedQueue;
35 import java.util.concurrent.ExecutionException;
36 import java.util.concurrent.Future;
37 import java.util.concurrent.atomic.AtomicLong;
38 import java.util.function.Consumer;
39 import java.util.stream.Collectors;
40
41 import org.apache.hc.client5.http.AuthenticationStrategy;
42 import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
43 import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
44 import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
45 import org.apache.hc.client5.http.auth.AuthCache;
46 import org.apache.hc.client5.http.auth.AuthSchemeFactory;
47 import org.apache.hc.client5.http.auth.AuthScope;
48 import org.apache.hc.client5.http.auth.CredentialsProvider;
49 import org.apache.hc.client5.http.auth.StandardAuthScheme;
50 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
51 import org.apache.hc.client5.http.config.RequestConfig;
52 import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
53 import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
54 import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
55 import org.apache.hc.client5.http.impl.auth.BasicScheme;
56 import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder;
57 import org.apache.hc.client5.http.protocol.HttpClientContext;
58 import org.apache.hc.client5.testing.BasicTestAuthenticator;
59 import org.apache.hc.client5.testing.auth.Authenticator;
60 import org.apache.hc.core5.function.Decorator;
61 import org.apache.hc.core5.http.ContentType;
62 import org.apache.hc.core5.http.HttpHeaders;
63 import org.apache.hc.core5.http.HttpHost;
64 import org.apache.hc.core5.http.HttpRequestInterceptor;
65 import org.apache.hc.core5.http.HttpResponse;
66 import org.apache.hc.core5.http.HttpResponseInterceptor;
67 import org.apache.hc.core5.http.HttpStatus;
68 import org.apache.hc.core5.http.ProtocolException;
69 import org.apache.hc.core5.http.URIScheme;
70 import org.apache.hc.core5.http.config.Lookup;
71 import org.apache.hc.core5.http.config.Registry;
72 import org.apache.hc.core5.http.config.RegistryBuilder;
73 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
74 import org.apache.hc.core5.http.support.BasicResponseBuilder;
75 import org.apache.hc.core5.net.URIAuthority;
76 import org.apache.hc.core5.testing.nio.H2TestServer;
77 import org.hamcrest.CoreMatchers;
78 import org.junit.jupiter.api.Assertions;
79 import org.junit.jupiter.api.Test;
80 import org.mockito.Mockito;
81
82 public abstract class AbstractHttpAsyncClientAuthenticationTest<T extends CloseableHttpAsyncClient> extends AbstractIntegrationTestBase {
83
84 public AbstractHttpAsyncClientAuthenticationTest(final URIScheme scheme) {
85 super(scheme);
86 }
87
88 abstract protected H2TestServer startServer(final Decorator<AsyncServerExchangeHandler> exchangeHandlerDecorator) throws Exception;
89
90 protected H2TestServer startServer() throws Exception {
91 return startServer(requestHandler -> new AuthenticatingAsyncDecorator(requestHandler, new BasicTestAuthenticator("test:test", "test realm")));
92 }
93
94 interface TestClientBuilder {
95
96 TestClientBuilder setDefaultAuthSchemeRegistry(Lookup<AuthSchemeFactory> authSchemeRegistry);
97
98 TestClientBuilder setTargetAuthenticationStrategy(AuthenticationStrategy targetAuthStrategy);
99
100 TestClientBuilder addResponseInterceptor(HttpResponseInterceptor responseInterceptor);
101
102 TestClientBuilder addRequestInterceptor(HttpRequestInterceptor requestInterceptor);
103
104 }
105
106 abstract protected T startClientCustom(final Consumer<TestClientBuilder> clientCustomizer) throws Exception;
107
108 T startClient() throws Exception {
109 return startClientCustom(c -> {});
110 }
111
112 @Test
113 public void testBasicAuthenticationNoCreds() throws Exception {
114 final H2TestServer server = startServer();
115 server.register("*", AsyncEchoHandler::new);
116 final HttpHost target = targetHost();
117
118 final T client = startClient();
119
120 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
121 final HttpClientContext context = HttpClientContext.create();
122 context.setCredentialsProvider(credsProvider);
123
124 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
125 .setHttpHost(target)
126 .setPath("/")
127 .build(), context, null);
128 final HttpResponse response = future.get();
129
130 Assertions.assertNotNull(response);
131 Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
132 Mockito.verify(credsProvider).getCredentials(
133 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
134 }
135
136 @Test
137 public void testBasicAuthenticationFailure() throws Exception {
138 final H2TestServer server = startServer();
139 server.register("*", AsyncEchoHandler::new);
140 final HttpHost target = targetHost();
141
142 final T client = startClient();
143
144 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
145 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
146 .thenReturn(new UsernamePasswordCredentials("test", "all-wrong".toCharArray()));
147 final HttpClientContext context = HttpClientContext.create();
148 context.setCredentialsProvider(credsProvider);
149
150 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
151 .setHttpHost(target)
152 .setPath("/")
153 .build(), context, null);
154 final HttpResponse response = future.get();
155
156 Assertions.assertNotNull(response);
157 Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
158 Mockito.verify(credsProvider).getCredentials(
159 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
160 }
161
162 @Test
163 public void testBasicAuthenticationSuccess() throws Exception {
164 final H2TestServer server = startServer();
165 server.register("*", AsyncEchoHandler::new);
166 final HttpHost target = targetHost();
167
168 final T client = startClient();
169
170 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
171 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
172 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
173 final HttpClientContext context = HttpClientContext.create();
174 context.setCredentialsProvider(credsProvider);
175
176 final Future<SimpleHttpResponse> future = client.execute(
177 SimpleRequestBuilder.get()
178 .setHttpHost(target)
179 .setPath("/")
180 .build(), context, null);
181 final HttpResponse response = future.get();
182
183 Assertions.assertNotNull(response);
184 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
185 Mockito.verify(credsProvider).getCredentials(
186 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
187 }
188
189 @Test
190 public void testBasicAuthenticationWithEntitySuccess() throws Exception {
191 final H2TestServer server = startServer();
192 server.register("*", AsyncEchoHandler::new);
193 final HttpHost target = targetHost();
194
195 final T client = startClient();
196
197 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
198 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
199 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
200 final HttpClientContext context = HttpClientContext.create();
201 context.setCredentialsProvider(credsProvider);
202 final Future<SimpleHttpResponse> future = client.execute(
203 SimpleRequestBuilder.put()
204 .setHttpHost(target)
205 .setPath("/")
206 .setBody("Some important stuff", ContentType.TEXT_PLAIN)
207 .build(), context, null);
208 final HttpResponse response = future.get();
209
210 Assertions.assertNotNull(response);
211 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
212 Mockito.verify(credsProvider).getCredentials(
213 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
214 }
215
216 @Test
217 public void testBasicAuthenticationExpectationFailure() throws Exception {
218 final H2TestServer server = startServer();
219 server.register("*", AsyncEchoHandler::new);
220 final HttpHost target = targetHost();
221
222 final T client = startClient();
223
224 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
225 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
226 .thenReturn(new UsernamePasswordCredentials("test", "all-wrong".toCharArray()));
227 final HttpClientContext context = HttpClientContext.create();
228 context.setCredentialsProvider(credsProvider);
229 context.setRequestConfig(RequestConfig.custom().setExpectContinueEnabled(true).build());
230 final Future<SimpleHttpResponse> future = client.execute(
231 SimpleRequestBuilder.put()
232 .setHttpHost(target)
233 .setPath("/")
234 .setBody("Some important stuff", ContentType.TEXT_PLAIN)
235 .build(), context, null);
236 final HttpResponse response = future.get();
237
238 Assertions.assertNotNull(response);
239 Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
240 }
241
242 @Test
243 public void testBasicAuthenticationExpectationSuccess() throws Exception {
244 final H2TestServer server = startServer();
245 server.register("*", AsyncEchoHandler::new);
246 final HttpHost target = targetHost();
247
248 final T client = startClient();
249
250 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
251 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
252 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
253 final HttpClientContext context = HttpClientContext.create();
254 context.setCredentialsProvider(credsProvider);
255 context.setRequestConfig(RequestConfig.custom().setExpectContinueEnabled(true).build());
256 final Future<SimpleHttpResponse> future = client.execute(
257 SimpleRequestBuilder.put()
258 .setHttpHost(target)
259 .setPath("/")
260 .setBody("Some important stuff", ContentType.TEXT_PLAIN)
261 .build(), context, null);
262 final HttpResponse response = future.get();
263
264 Assertions.assertNotNull(response);
265 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
266 Mockito.verify(credsProvider).getCredentials(
267 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
268 }
269
270 @Test
271 public void testBasicAuthenticationCredentialsCaching() throws Exception {
272 final H2TestServer server = startServer();
273 server.register("*", AsyncEchoHandler::new);
274 final HttpHost target = targetHost();
275
276 final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
277 final T client = startClientCustom(builder -> builder.setTargetAuthenticationStrategy(authStrategy));
278
279 final HttpClientContext context = HttpClientContext.create();
280 context.setCredentialsProvider(CredentialsProviderBuilder.create()
281 .add(target, "test", "test".toCharArray())
282 .build());
283
284 for (int i = 0; i < 5; i++) {
285 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
286 .setHttpHost(target)
287 .setPath("/")
288 .build(), context, null);
289 final HttpResponse response = future.get();
290 Assertions.assertNotNull(response);
291 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
292 }
293
294 Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
295 }
296
297 @Test
298 public void testBasicAuthenticationCredentialsCachingByPathPrefix() throws Exception {
299 final H2TestServer server = startServer();
300 server.register("*", AsyncEchoHandler::new);
301 final HttpHost target = targetHost();
302
303 final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
304 final Queue<HttpResponse> responseQueue = new ConcurrentLinkedQueue<>();
305
306 final T client = startClientCustom(builder -> builder
307 .setTargetAuthenticationStrategy(authStrategy)
308 .addResponseInterceptor((response, entity, context)
309 -> responseQueue.add(BasicResponseBuilder.copy(response).build())));
310
311 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
312 .add(target, "test", "test".toCharArray())
313 .build();
314
315 final AuthCache authCache = new BasicAuthCache();
316
317 for (final String requestPath: new String[] {"/blah/a", "/blah/b?huh", "/blah/c", "/bl%61h/%61"}) {
318 final HttpClientContext context = HttpClientContext.create();
319 context.setAuthCache(authCache);
320 context.setCredentialsProvider(credentialsProvider);
321 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
322 .setHttpHost(target)
323 .setPath(requestPath)
324 .build(), context, null);
325 final HttpResponse response = future.get();
326 Assertions.assertNotNull(response);
327 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
328 }
329
330
331 Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
332
333 assertThat(
334 responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
335 CoreMatchers.equalTo(Arrays.asList(401, 200, 200, 200, 200)));
336
337 responseQueue.clear();
338 authCache.clear();
339 Mockito.reset(authStrategy);
340
341 for (final String requestPath: new String[] {"/blah/a", "/yada/a", "/blah/blah/"}) {
342 final HttpClientContext context = HttpClientContext.create();
343 context.setCredentialsProvider(credentialsProvider);
344 context.setAuthCache(authCache);
345 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
346 .setHttpHost(target)
347 .setPath(requestPath)
348 .build(), context, null);
349 final HttpResponse response = future.get();
350 Assertions.assertNotNull(response);
351 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
352 }
353
354
355 Mockito.verify(authStrategy, Mockito.times(3)).select(Mockito.any(), Mockito.any(), Mockito.any());
356
357 assertThat(
358 responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
359 CoreMatchers.equalTo(Arrays.asList(401, 200, 401, 200, 401, 200)));
360 }
361
362 @Test
363 public void testAuthenticationUserinfoInRequestFailure() throws Exception {
364 final H2TestServer server = startServer();
365 server.register("*", AsyncEchoHandler::new);
366 final HttpHost target = targetHost();
367
368 final T client = startClient();
369
370 final HttpClientContext context = HttpClientContext.create();
371 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
372 .setScheme(target.getSchemeName())
373 .setAuthority(new URIAuthority("test:test", target.getHostName(), target.getPort()))
374 .setPath("/")
375 .build(), context, null);
376 final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> future.get());
377 assertThat(exception.getCause(), CoreMatchers.instanceOf(ProtocolException.class));
378 }
379
380 @Test
381 public void testReauthentication() throws Exception {
382 final Authenticator authenticator = new BasicTestAuthenticator("test:test", "test realm") {
383
384 private final AtomicLong count = new AtomicLong(0);
385
386 @Override
387 public boolean authenticate(final URIAuthority authority, final String requestUri, final String credentials) {
388 final boolean authenticated = super.authenticate(authority, requestUri, credentials);
389 if (authenticated) {
390 return this.count.incrementAndGet() % 4 != 0;
391 }
392 return false;
393 }
394 };
395
396 final H2TestServer server = startServer(exchangeHandler -> new AuthenticatingAsyncDecorator(exchangeHandler, authenticator) {
397
398 @Override
399 protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
400 unauthorized.removeHeaders(HttpHeaders.WWW_AUTHENTICATE);
401 unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, "MyBasic realm=\"test realm\"");
402 }
403
404 });
405 server.register("*", AsyncEchoHandler::new);
406 final HttpHost target = targetHost();
407
408 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
409 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
410 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
411
412 final Registry<AuthSchemeFactory> authSchemeRegistry = RegistryBuilder.<AuthSchemeFactory>create()
413 .register("MyBasic", context -> new BasicScheme() {
414
415 private static final long serialVersionUID = 1L;
416
417 @Override
418 public String getName() {
419 return "MyBasic";
420 }
421
422 })
423 .build();
424
425 final T client = startClientCustom(builder -> builder.setDefaultAuthSchemeRegistry(authSchemeRegistry));
426
427 final RequestConfig config = RequestConfig.custom()
428 .setTargetPreferredAuthSchemes(Collections.singletonList("MyBasic"))
429 .build();
430 final HttpClientContext context = HttpClientContext.create();
431 context.setCredentialsProvider(credsProvider);
432
433 for (int i = 0; i < 10; i++) {
434 final SimpleHttpRequest request = SimpleRequestBuilder.get()
435 .setHttpHost(target)
436 .setPath("/")
437 .build();
438 request.setConfig(config);
439 final Future<SimpleHttpResponse> future = client.execute(request, context, null);
440 final SimpleHttpResponse response = future.get();
441 Assertions.assertNotNull(response);
442 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
443 }
444 }
445
446 @Test
447 public void testAuthenticationFallback() throws Exception {
448 final H2TestServer server = startServer(exchangeHandler -> new AuthenticatingAsyncDecorator(exchangeHandler, new BasicTestAuthenticator("test:test", "test realm")) {
449
450 @Override
451 protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
452 unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.DIGEST + " realm=\"test realm\" invalid");
453 }
454
455 });
456 server.register("*", AsyncEchoHandler::new);
457 final HttpHost target = targetHost();
458
459 final T client = startClient();
460
461 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
462 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
463 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
464 final HttpClientContext context = HttpClientContext.create();
465 context.setCredentialsProvider(credsProvider);
466
467 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
468 .setHttpHost(target)
469 .setPath("/")
470 .build(), context, null);
471 final SimpleHttpResponse response = future.get();
472 Assertions.assertNotNull(response);
473 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
474 Mockito.verify(credsProvider).getCredentials(
475 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
476 }
477
478 }