22
22
import java .nio .ByteBuffer ;
23
23
import java .util .ArrayList ;
24
24
import java .util .Collections ;
25
+ import java .util .HashMap ;
25
26
import java .util .HashSet ;
26
27
import java .util .List ;
27
28
import java .util .Locale ;
28
29
import java .util .Map ;
30
+ import java .util .Objects ;
29
31
import java .util .Optional ;
30
32
import java .util .Set ;
31
33
import java .util .concurrent .atomic .AtomicReference ;
@@ -253,12 +255,13 @@ public void process() throws TransportException {
253
255
proxyResponse .set (null );
254
256
255
257
final boolean isSuccess = proxyHandler .validateProxyResponse (connectResponse );
256
- // When connecting to proxy, it does not challenge us for authentication. If the user has specified
257
- // a configuration, and it is not NONE, then we fail due to misconfiguration.
258
258
if (isSuccess ) {
259
- if (proxyConfiguration == null || proxyConfiguration .authentication () == ProxyAuthenticationType .NONE ) {
259
+ if (proxyConfiguration == null || proxyConfiguration .authentication () == ProxyAuthenticationType .NONE
260
+ || proxyConfiguration .getAuthenticator () == null ) {
260
261
proxyState = ProxyState .PN_PROXY_CONNECTED ;
261
262
} else {
263
+ // In response to CONNECT, the proxy didn't challenge us for authentication. Given the user has specified an
264
+ // authentication configuration, we fail due to misconfiguration.
262
265
if (LOGGER .isErrorEnabled ()) {
263
266
LOGGER .error ("ProxyConfiguration mismatch. User configured: '{}', but authentication is not required" ,
264
267
proxyConfiguration .authentication ());
@@ -269,29 +272,47 @@ public void process() throws TransportException {
269
272
}
270
273
271
274
final Map <String , List <String >> headers = connectResponse .getHeaders ();
272
- final Set <ProxyAuthenticationType > supportedTypes = getAuthenticationTypes (headers );
273
-
274
- // The proxy did not successfully connect, user has specified that they want a particular
275
- // authentication method, but it is not in list of supported authentication methods.
276
- if (proxyConfiguration != null && !supportedTypes .contains (proxyConfiguration .authentication ())) {
277
- if (LOGGER .isErrorEnabled ()) {
278
- LOGGER .error ("Proxy authentication required. User configured: '{}', but supported proxy authentication methods are: {}" ,
279
- proxyConfiguration .authentication (),
280
- supportedTypes .stream ().map (type -> type .toString ()).collect (Collectors .joining ("," )));
275
+ final List <String > challenges = headers .getOrDefault (PROXY_AUTHENTICATE , new ArrayList <>());
276
+ final ProxyChallengeProcessor processor ;
277
+ if (proxyConfiguration != null && proxyConfiguration .getAuthenticator () != null ) {
278
+ final boolean is407 = connectResponse .getStatus ().getStatusCode () == 407 ;
279
+ if (!is407 ) {
280
+ closeTailProxyError (PROXY_CONNECT_FAILED + connectResponse );
281
+ break ;
282
+ }
283
+ if (challenges .isEmpty ()) {
284
+ closeTailProxyError ("'407 Proxy Authentication Required' received without " + PROXY_AUTHENTICATE
285
+ + "header or authentication schemes." + connectResponse );
286
+ break ;
281
287
}
282
- closeTailProxyError (PROXY_CONNECT_USER_ERROR + PROXY_CONNECT_FAILED
288
+ processor = new DelegatedProxyChallengeProcessor (headers , proxyConfiguration .getAuthenticator ());
289
+ } else {
290
+ final Set <ProxyAuthenticationType > supportedTypes = getAuthenticationTypes (headers );
291
+ // The proxy did not successfully connect, user has specified that they want a particular
292
+ // authentication method, but it is not in list of supported authentication methods.
293
+ if (proxyConfiguration != null && !supportedTypes .contains (proxyConfiguration .authentication ())) {
294
+ if (LOGGER .isErrorEnabled ()) {
295
+ LOGGER .error (
296
+ "Proxy authentication required. User configured: '{}', but supported proxy authentication methods are: {}" ,
297
+ proxyConfiguration .authentication (),
298
+ supportedTypes .stream ().map (type -> type .toString ()).collect (Collectors .joining ("," )));
299
+ }
300
+ closeTailProxyError (PROXY_CONNECT_USER_ERROR + PROXY_CONNECT_FAILED
283
301
+ connectResponse );
284
- break ;
285
- }
286
-
287
- final List <String > challenges = headers .getOrDefault (PROXY_AUTHENTICATE , new ArrayList <>());
288
- final ProxyChallengeProcessor processor = proxyConfiguration != null
302
+ break ;
303
+ }
304
+ processor = proxyConfiguration != null
289
305
? getChallengeProcessor (host , challenges , proxyConfiguration .authentication ())
290
306
: getChallengeProcessor (host , challenges , supportedTypes );
307
+ }
291
308
292
309
if (processor != null ) {
293
310
proxyState = ProxyState .PN_PROXY_CHALLENGE ;
294
311
ProxyImpl .this .headers = processor .getHeader ();
312
+ if (proxyConfiguration != null
313
+ && proxyConfiguration .getAuthenticator () != null && !testAuthorizeHeader (challenges , ProxyImpl .this .headers )) {
314
+ closeTailProxyError ("User error: ProxyAuthenticator did not provide a valid authorization header." );
315
+ }
295
316
} else {
296
317
LOGGER .warn ("Could not get ProxyChallengeProcessor for challenges." );
297
318
closeTailProxyError (PROXY_CONNECT_FAILED + String .join (";" , challenges ));
@@ -540,4 +561,39 @@ private ProxyResponse readProxyResponse(ByteBuffer buffer) {
540
561
return proxyResponse .get ();
541
562
}
542
563
}
564
+
565
+ private static final class DelegatedProxyChallengeProcessor implements ProxyChallengeProcessor {
566
+ private final Map <String , List <String >> headers ;
567
+ private final com .microsoft .azure .proton .transport .proxy .ProxyAuthenticator authenticator ;
568
+
569
+ DelegatedProxyChallengeProcessor (Map <String , List <String >> headers ,
570
+ com .microsoft .azure .proton .transport .proxy .ProxyAuthenticator authenticator ) {
571
+ this .headers = Objects .requireNonNull (headers , "'headers' cannot be null." );
572
+ this .authenticator = Objects .requireNonNull (authenticator , "'authenticator' cannot be null." );
573
+ }
574
+
575
+ @ Override
576
+ public Map <String , String > getHeader () {
577
+ // the call site ensured that the 'headers' contain 'Proxy-Authenticate' header with the authentication schemes.
578
+ final String authorizedHeader = authenticator .authenticate (ChallengeResponseAccessHelper .internalCreate (headers ));
579
+ final Map <String , String > headers = new HashMap <>(1 );
580
+ headers .put (Constants .PROXY_AUTHORIZATION , authorizedHeader );
581
+ return headers ;
582
+ }
583
+ }
584
+
585
+ private boolean testAuthorizeHeader (List <String > challenges , Map <String , String > headers ) {
586
+ assert !challenges .isEmpty ();
587
+ final String authorizeHeader = headers .get (Constants .PROXY_AUTHORIZATION );
588
+ if (authorizeHeader == null || authorizeHeader .trim ().isEmpty ()) {
589
+ return false ;
590
+ }
591
+ final String value = authorizeHeader .toLowerCase (Locale .ROOT );
592
+ for (String scheme : challenges ) {
593
+ if (value .startsWith (scheme .toLowerCase (Locale .ROOT ) + " " )) {
594
+ return true ;
595
+ }
596
+ }
597
+ return false ;
598
+ }
543
599
}
0 commit comments