Skip to content

Commit e000495

Browse files
authored
Add proxying support to http-netty4 (Azure#44935)
Add proxying support to http-netty4
1 parent 59f95a2 commit e000495

30 files changed

+1472
-673
lines changed

sdk/clientcore/core/src/main/java/io/clientcore/core/utils/BasicChallengeHandler.java

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44
package io.clientcore.core.utils;
55

66
import io.clientcore.core.http.models.HttpHeaderName;
7+
import io.clientcore.core.http.models.HttpHeaders;
78
import io.clientcore.core.http.models.HttpRequest;
89
import io.clientcore.core.http.models.Response;
910
import io.clientcore.core.models.binarydata.BinaryData;
1011

12+
import java.net.URI;
1113
import java.nio.charset.StandardCharsets;
14+
import java.util.AbstractMap;
1215
import java.util.Base64;
1316
import java.util.List;
17+
import java.util.Map;
18+
import java.util.Objects;
1419

1520
import static io.clientcore.core.utils.AuthUtils.BASIC;
1621

@@ -37,33 +42,61 @@ public BasicChallengeHandler(String username, String password) {
3742
@Override
3843
public void handleChallenge(HttpRequest request, Response<BinaryData> response, boolean isProxy) {
3944
if (canHandle(response, isProxy)) {
40-
synchronized (request.getHeaders()) {
41-
HttpHeaderName headerName = isProxy ? HttpHeaderName.PROXY_AUTHORIZATION : HttpHeaderName.AUTHORIZATION;
42-
// Check if the appropriate Authorization header is already present
43-
if (request.getHeaders().getValue(headerName) == null) {
44-
request.getHeaders().add(headerName, authHeader);
45-
}
46-
}
45+
HttpHeaderName headerName = isProxy ? HttpHeaderName.PROXY_AUTHORIZATION : HttpHeaderName.AUTHORIZATION;
46+
request.getHeaders().set(headerName, authHeader);
4747
}
4848
}
4949

5050
@Override
5151
public boolean canHandle(Response<BinaryData> response, boolean isProxy) {
52-
if (response.getHeaders() != null) {
53-
HttpHeaderName authHeaderName
54-
= isProxy ? HttpHeaderName.PROXY_AUTHENTICATE : HttpHeaderName.WWW_AUTHENTICATE;
55-
String authHeader = response.getHeaders().getValue(authHeaderName);
56-
57-
if (authHeader != null) {
58-
// Parse the authenticate header into AuthenticateChallenges, then check if any use scheme 'Basic'.
59-
List<AuthenticateChallenge> challenges = AuthUtils.parseAuthenticateHeader(authHeader);
60-
for (AuthenticateChallenge challenge : challenges) {
61-
if (BASIC.equalsIgnoreCase(challenge.getScheme())) {
62-
return true;
63-
}
52+
HttpHeaders responseHeaders = response.getHeaders();
53+
if (responseHeaders == null) {
54+
return false;
55+
}
56+
57+
HttpHeaderName authHeaderName = isProxy ? HttpHeaderName.PROXY_AUTHENTICATE : HttpHeaderName.WWW_AUTHENTICATE;
58+
List<String> authenticateHeaders = responseHeaders.getValues(authHeaderName);
59+
if (CoreUtils.isNullOrEmpty(authenticateHeaders)) {
60+
return false;
61+
}
62+
63+
for (String authenticateHeader : authenticateHeaders) {
64+
for (AuthenticateChallenge challenge : AuthUtils.parseAuthenticateHeader(authenticateHeader)) {
65+
if (canHandle(challenge)) {
66+
return true;
6467
}
6568
}
6669
}
70+
71+
return false;
72+
}
73+
74+
@Override
75+
public Map.Entry<String, AuthenticateChallenge> handleChallenge(String method, URI uri,
76+
List<AuthenticateChallenge> challenges) {
77+
Objects.requireNonNull(challenges, "Cannot use a null 'challenges' to handle challenges.");
78+
for (AuthenticateChallenge challenge : challenges) {
79+
if (canHandle(challenge)) {
80+
return new AbstractMap.SimpleImmutableEntry<>(authHeader, challenge);
81+
}
82+
}
83+
84+
return null;
85+
}
86+
87+
@Override
88+
public boolean canHandle(List<AuthenticateChallenge> challenges) {
89+
Objects.requireNonNull(challenges, "Cannot use a null 'challenges' to determine if it can be handled.");
90+
for (AuthenticateChallenge challenge : challenges) {
91+
if (canHandle(challenge)) {
92+
return true;
93+
}
94+
}
95+
6796
return false;
6897
}
98+
99+
private static boolean canHandle(AuthenticateChallenge challenge) {
100+
return challenge != null && BASIC.equalsIgnoreCase(challenge.getScheme());
101+
}
69102
}

sdk/clientcore/core/src/main/java/io/clientcore/core/utils/ChallengeHandler.java

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
import io.clientcore.core.http.models.Response;
88
import io.clientcore.core.models.binarydata.BinaryData;
99

10+
import java.net.URI;
1011
import java.util.Arrays;
12+
import java.util.List;
13+
import java.util.Map;
1114

1215
/**
1316
* Class representing a challenge handler for authentication.
@@ -19,19 +22,63 @@ public interface ChallengeHandler {
1922
* @param request The HTTP request to be updated with authentication info.
2023
* @param response The HTTP response containing the authentication challenge.
2124
* @param isProxy Indicates if the challenge is for a proxy.
25+
* @throws NullPointerException If {@code request} or {@code response} is null.
2226
*/
2327
void handleChallenge(HttpRequest request, Response<BinaryData> response, boolean isProxy);
2428

2529
/**
26-
* Validate if this ChallengeHandler can handle the provided challenge
27-
* by inspecting the 'Proxy-Authenticate' or 'WWW-Authenticate' headers.
30+
* Validate if this ChallengeHandler can handle the provided challenge by inspecting the {@code Proxy-Authenticate}
31+
* or {@code WWW-Authenticate} headers.
32+
* <p>
33+
* Use of {@code Proxy-Authenticate} or {@code WWW-Authenticate} is based on {@code isProxy}.
34+
* <p>
35+
* This method will return true if at least one of the authenticate challenges in the response header can be handled
36+
* by this ChallengeHandler. Meaning, if {@link #of(ChallengeHandler...)} is used ordering of the provided
37+
* ChallengeHandlers is important, where if more secure handling is needed positioning
38+
* {@link DigestChallengeHandler}, or a custom ChallengeHandler for more secure auth mechanisms, before
39+
* {@link BasicChallengeHandler} is necessary.
2840
*
2941
* @param response The HTTP response containing the authentication challenge.
3042
* @param isProxy boolean indicating if it is a proxy challenge handler.
31-
* @return boolean indicating if the challenge can be handled.
43+
* @return Whether a challenge within the {@link Response#getHeaders()} {@code Proxy-Authenticate} or
44+
* {@code WWW-Authenticate} can be handled.
45+
* @throws NullPointerException If {@code response} is null.
3246
*/
3347
boolean canHandle(Response<BinaryData> response, boolean isProxy);
3448

49+
/**
50+
* Creates a {@code Proxy-Authorization} or {@code WWW-Authorization} compliant header from the given
51+
* {@link AuthenticateChallenge}s.
52+
* <p>
53+
* It is left to the ChallengeHandler implementation to decide which of the {@link AuthenticateChallenge}s it
54+
* {@link #canHandle(List)} to use when creating the header.
55+
* <p>
56+
* If none of the {@link AuthenticateChallenge}s can be handled null will be returned.
57+
*
58+
* @param method The HTTP method used in the request being authorized.
59+
* @param uri The URI for the HTTP request being authorized.
60+
* @param challenges The HTTP authenticate challenges, either from {@code Proxy-Authenticate} or
61+
* {@code WWW-Authenticate} headers.
62+
* @return A {@link Map.Entry} where the key is the {@code Proxy-Authorization} or {@code WWW-Authorization}
63+
* compliant header and value is the {@link AuthenticateChallenge} used to generate the header, or null if none of
64+
* the challenges can be handled.
65+
*/
66+
Map.Entry<String, AuthenticateChallenge> handleChallenge(String method, URI uri,
67+
List<AuthenticateChallenge> challenges);
68+
69+
/**
70+
* Validate if this ChallengeHandler can handle any of the provided {@link AuthenticateChallenge}s.
71+
* <p>
72+
* This method is meant for scenarios where authenticate headers are processed into {@link AuthenticateChallenge}s
73+
* externally, normally using {@link AuthUtils#parseAuthenticateHeader(String)}.
74+
*
75+
* @param challenges The HTTP authenticate challenges, either from {@code Proxy-Authenticate} or
76+
* {@code WWW-Authenticate} headers.
77+
* @return Whether any {@link AuthenticateChallenge} can be handled.
78+
* @throws NullPointerException If {@code challenges} is null.
79+
*/
80+
boolean canHandle(List<AuthenticateChallenge> challenges);
81+
3582
/**
3683
* Factory method for creating composite handlers.
3784
*

sdk/clientcore/core/src/main/java/io/clientcore/core/utils/CompositeChallengeHandler.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
import io.clientcore.core.instrumentation.logging.ClientLogger;
88
import io.clientcore.core.models.binarydata.BinaryData;
99

10+
import java.net.URI;
1011
import java.util.List;
12+
import java.util.Map;
13+
import java.util.Objects;
1114

1215
final class CompositeChallengeHandler implements ChallengeHandler {
1316
private static final ClientLogger LOGGER = new ClientLogger(CompositeChallengeHandler.class);
@@ -50,4 +53,30 @@ public void handleChallenge(HttpRequest request, Response<BinaryData> response,
5053
LOGGER.logThrowableAsError(
5154
new UnsupportedOperationException("None of the challenge handlers could handle the challenge."));
5255
}
56+
57+
@Override
58+
public Map.Entry<String, AuthenticateChallenge> handleChallenge(String method, URI uri,
59+
List<AuthenticateChallenge> challenges) {
60+
Objects.requireNonNull(challenges, "Cannot use a null 'challenges' to handle challenges.");
61+
for (ChallengeHandler handler : challengeHandlers) {
62+
Map.Entry<String, AuthenticateChallenge> result = handler.handleChallenge(method, uri, challenges);
63+
if (result != null) {
64+
return result;
65+
}
66+
}
67+
68+
return null;
69+
}
70+
71+
@Override
72+
public boolean canHandle(List<AuthenticateChallenge> challenges) {
73+
Objects.requireNonNull(challenges, "Cannot use a null 'challenges' to determine if it can be handled.");
74+
for (ChallengeHandler handler : challengeHandlers) {
75+
if (handler.canHandle(challenges)) {
76+
return true;
77+
}
78+
}
79+
80+
return false;
81+
}
5382
}

0 commit comments

Comments
 (0)