Skip to content

Commit 0b6ac24

Browse files
authored
Update a couple bearer token policies to use CoreUtils.parseAuthenticateHeader (#46927)
* Use CoreUtils.parseAuthenticateHeader to resolve challenge issue * Also update Key Vault * Fix linting
1 parent 7c73ec0 commit 0b6ac24

5 files changed

Lines changed: 87 additions & 193 deletions

File tree

sdk/containerregistry/azure-containers-containerregistry/src/main/java/com/azure/containers/containerregistry/implementation/authentication/ContainerRegistryCredentialsPolicy.java

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import com.azure.core.http.HttpPipelineCallContext;
99
import com.azure.core.http.HttpResponse;
1010
import com.azure.core.http.policy.BearerTokenAuthenticationPolicy;
11+
import com.azure.core.util.AuthenticateChallenge;
12+
import com.azure.core.util.CoreUtils;
1113
import reactor.core.publisher.Mono;
1214

1315
/**
@@ -71,12 +73,10 @@ public Mono<Boolean> authorizeRequestOnChallenge(HttpPipelineCallContext context
7173
if (!(response.getStatusCode() == 401 && authHeader != null)) {
7274
return Mono.just(false);
7375
} else {
74-
String scope = extractValue(authHeader, SCOPES_PARAMETER);
75-
String serviceName = extractValue(authHeader, SERVICE_PARAMETER);
76+
ContainerRegistryTokenRequestContext tokenRequestContext = createTokenRequestContext(authHeader);
7677

77-
if (scope != null && serviceName != null) {
78-
return setAuthorizationHeader(context, new ContainerRegistryTokenRequestContext(serviceName, scope))
79-
.thenReturn(true);
78+
if (tokenRequestContext != null) {
79+
return setAuthorizationHeader(context, tokenRequestContext).thenReturn(true);
8080
}
8181
return Mono.just(false);
8282
}
@@ -86,7 +86,6 @@ public Mono<Boolean> authorizeRequestOnChallenge(HttpPipelineCallContext context
8686
* Executed before sending the initial request and authenticates the request.
8787
*
8888
* @param context The request context.
89-
* @return A {@link Mono} containing {@link Void}
9089
*/
9190
@Override
9291
public void authorizeRequestSync(HttpPipelineCallContext context) {
@@ -111,39 +110,28 @@ public boolean authorizeRequestOnChallengeSync(HttpPipelineCallContext context,
111110
if (!(response.getStatusCode() == 401 && authHeader != null)) {
112111
return false;
113112
} else {
114-
String scope = extractValue(authHeader, SCOPES_PARAMETER);
115-
String serviceName = extractValue(authHeader, SERVICE_PARAMETER);
116-
117-
if (scope != null && serviceName != null) {
118-
setAuthorizationHeaderSync(context, new ContainerRegistryTokenRequestContext(serviceName, scope));
113+
ContainerRegistryTokenRequestContext tokenRequestContext = createTokenRequestContext(authHeader);
114+
if (tokenRequestContext != null) {
115+
setAuthorizationHeaderSync(context, tokenRequestContext);
119116
return true;
120117
}
121118
}
122119

123120
return false;
124121
}
125122

126-
/**
127-
* Extracts value for given key in www-authenticate header.
128-
* Expects key="value" format and return value without quotes.
129-
*
130-
* returns if value is not found
131-
*/
132-
private String extractValue(String authHeader, String key) {
133-
int start = authHeader.indexOf(key);
134-
if (start < 0 || authHeader.length() - start < key.length() + 3) {
135-
return null;
136-
}
123+
private static ContainerRegistryTokenRequestContext createTokenRequestContext(String authHeader) {
124+
AuthenticateChallenge bearerChallenge = CoreUtils.parseAuthenticateHeader(authHeader)
125+
.stream()
126+
.filter(challenge -> "Bearer".equalsIgnoreCase(challenge.getScheme()))
127+
.findFirst()
128+
.orElse(null);
137129

138-
start += key.length();
139-
if (authHeader.charAt(start) == '=' && authHeader.charAt(start + 1) == '"') {
140-
start += 2;
141-
int end = authHeader.indexOf('"', start);
142-
if (end > start) {
143-
return authHeader.substring(start, end);
144-
}
145-
}
130+
String scope = bearerChallenge.getParameters().get(SCOPES_PARAMETER);
131+
String serviceName = bearerChallenge.getParameters().get(SERVICE_PARAMETER);
146132

147-
return null;
133+
return (scope != null && serviceName != null)
134+
? new ContainerRegistryTokenRequestContext(serviceName, scope)
135+
: null;
148136
}
149137
}

sdk/keyvault/azure-security-keyvault-administration/src/main/java/com/azure/security/keyvault/administration/implementation/KeyVaultCredentialPolicy.java

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.azure.core.http.HttpRequest;
1111
import com.azure.core.http.HttpResponse;
1212
import com.azure.core.http.policy.BearerTokenAuthenticationPolicy;
13+
import com.azure.core.util.AuthenticateChallenge;
1314
import com.azure.core.util.Base64Util;
1415
import com.azure.core.util.BinaryData;
1516
import com.azure.core.util.CoreUtils;
@@ -23,8 +24,6 @@
2324
import java.nio.ByteBuffer;
2425
import java.nio.charset.StandardCharsets;
2526
import java.util.Collections;
26-
import java.util.HashMap;
27-
import java.util.Locale;
2827
import java.util.Map;
2928
import java.util.Optional;
3029
import java.util.concurrent.ConcurrentHashMap;
@@ -41,7 +40,6 @@
4140
*/
4241
public class KeyVaultCredentialPolicy extends BearerTokenAuthenticationPolicy {
4342
private static final ClientLogger LOGGER = new ClientLogger(KeyVaultCredentialPolicy.class);
44-
private static final String BEARER_TOKEN_PREFIX = "Bearer ";
4543
private static final String KEY_VAULT_STASHED_CONTENT_KEY = "KeyVaultCredentialPolicyStashedBody";
4644
private static final String KEY_VAULT_STASHED_CONTENT_LENGTH_KEY = "KeyVaultCredentialPolicyStashedContentLength";
4745
private static final ConcurrentMap<String, ChallengeParameters> CHALLENGE_CACHE = new ConcurrentHashMap<>();
@@ -63,40 +61,20 @@ public KeyVaultCredentialPolicy(TokenCredential credential, boolean disableChall
6361
* Extracts attributes off the bearer challenge in the authentication header.
6462
*
6563
* @param authenticateHeader The authentication header containing the challenge.
66-
* @param authChallengePrefix The authentication challenge name.
67-
*
6864
* @return A challenge attributes map.
6965
*/
70-
private static Map<String, String> extractChallengeAttributes(String authenticateHeader,
71-
String authChallengePrefix) {
72-
if (!isBearerChallenge(authenticateHeader, authChallengePrefix)) {
66+
private static Map<String, String> extractChallengeAttributes(String authenticateHeader) {
67+
AuthenticateChallenge bearerChallenge = CoreUtils.parseAuthenticateHeader(authenticateHeader)
68+
.stream()
69+
.filter(challenge -> "Bearer".equalsIgnoreCase(challenge.getScheme()))
70+
.findFirst()
71+
.orElse(null);
72+
73+
if (bearerChallenge == null) {
7374
return Collections.emptyMap();
7475
}
7576

76-
String[] attributes = authenticateHeader.replace("\"", "").substring(authChallengePrefix.length()).split(",");
77-
Map<String, String> attributeMap = new HashMap<>();
78-
79-
for (String pair : attributes) {
80-
// Using trim is ugly, but we need it here because currently the 'claims' attribute comes after two spaces.
81-
String[] keyValue = pair.trim().split("=", 2);
82-
83-
attributeMap.put(keyValue[0], keyValue[1]);
84-
}
85-
86-
return attributeMap;
87-
}
88-
89-
/**
90-
* Verifies whether a challenge is bearer or not.
91-
*
92-
* @param authenticateHeader The authentication header containing all the challenges.
93-
* @param authChallengePrefix The authentication challenge name.
94-
*
95-
* @return A boolean indicating if the challenge is a bearer challenge or not.
96-
*/
97-
private static boolean isBearerChallenge(String authenticateHeader, String authChallengePrefix) {
98-
return (!CoreUtils.isNullOrEmpty(authenticateHeader)
99-
&& authenticateHeader.toLowerCase(Locale.ROOT).startsWith(authChallengePrefix.toLowerCase(Locale.ROOT)));
77+
return bearerChallenge.getParameters();
10078
}
10179

10280
@Override
@@ -154,7 +132,7 @@ public Mono<Boolean> authorizeRequestOnChallenge(HttpPipelineCallContext context
154132

155133
String authority = getRequestAuthority(request);
156134
Map<String, String> challengeAttributes
157-
= extractChallengeAttributes(response.getHeaderValue(WWW_AUTHENTICATE), BEARER_TOKEN_PREFIX);
135+
= extractChallengeAttributes(response.getHeaderValue(WWW_AUTHENTICATE));
158136
String scope = challengeAttributes.get("resource");
159137

160138
if (scope != null) {
@@ -271,8 +249,7 @@ public boolean authorizeRequestOnChallengeSync(HttpPipelineCallContext context,
271249
}
272250

273251
String authority = getRequestAuthority(request);
274-
Map<String, String> challengeAttributes
275-
= extractChallengeAttributes(response.getHeaderValue(WWW_AUTHENTICATE), BEARER_TOKEN_PREFIX);
252+
Map<String, String> challengeAttributes = extractChallengeAttributes(response.getHeaderValue(WWW_AUTHENTICATE));
276253
String scope = challengeAttributes.get("resource");
277254

278255
if (scope != null) {
@@ -436,7 +413,7 @@ && isClaimsPresent(newResponse)
436413

437414
private boolean isClaimsPresent(HttpResponse httpResponse) {
438415
Map<String, String> challengeAttributes
439-
= extractChallengeAttributes(httpResponse.getHeaderValue(WWW_AUTHENTICATE), BEARER_TOKEN_PREFIX);
416+
= extractChallengeAttributes(httpResponse.getHeaderValue(WWW_AUTHENTICATE));
440417

441418
String error = challengeAttributes.get("error");
442419

@@ -517,10 +494,10 @@ private static boolean isChallengeResourceValid(HttpRequest request, String scop
517494
new RuntimeException(String.format("The challenge resource '%s' is not a valid URI.", scope), e));
518495
}
519496

497+
String host = request.getUrl().getHost();
498+
String toEndWith = "." + scopeUri.getHost();
499+
520500
// Returns false if the host specified in the scope does not match the requested domain.
521-
return request.getUrl()
522-
.getHost()
523-
.toLowerCase(Locale.ROOT)
524-
.endsWith("." + scopeUri.getHost().toLowerCase(Locale.ROOT));
501+
return host.regionMatches(true, host.length() - toEndWith.length(), toEndWith, 0, toEndWith.length());
525502
}
526503
}

sdk/keyvault/azure-security-keyvault-certificates/src/main/java/com/azure/security/keyvault/certificates/implementation/KeyVaultCredentialPolicy.java

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.azure.core.http.HttpRequest;
1111
import com.azure.core.http.HttpResponse;
1212
import com.azure.core.http.policy.BearerTokenAuthenticationPolicy;
13+
import com.azure.core.util.AuthenticateChallenge;
1314
import com.azure.core.util.Base64Util;
1415
import com.azure.core.util.BinaryData;
1516
import com.azure.core.util.CoreUtils;
@@ -23,8 +24,6 @@
2324
import java.nio.ByteBuffer;
2425
import java.nio.charset.StandardCharsets;
2526
import java.util.Collections;
26-
import java.util.HashMap;
27-
import java.util.Locale;
2827
import java.util.Map;
2928
import java.util.Optional;
3029
import java.util.concurrent.ConcurrentHashMap;
@@ -41,7 +40,6 @@
4140
*/
4241
public class KeyVaultCredentialPolicy extends BearerTokenAuthenticationPolicy {
4342
private static final ClientLogger LOGGER = new ClientLogger(KeyVaultCredentialPolicy.class);
44-
private static final String BEARER_TOKEN_PREFIX = "Bearer ";
4543
private static final String KEY_VAULT_STASHED_CONTENT_KEY = "KeyVaultCredentialPolicyStashedBody";
4644
private static final String KEY_VAULT_STASHED_CONTENT_LENGTH_KEY = "KeyVaultCredentialPolicyStashedContentLength";
4745
private static final ConcurrentMap<String, ChallengeParameters> CHALLENGE_CACHE = new ConcurrentHashMap<>();
@@ -63,40 +61,20 @@ public KeyVaultCredentialPolicy(TokenCredential credential, boolean disableChall
6361
* Extracts attributes off the bearer challenge in the authentication header.
6462
*
6563
* @param authenticateHeader The authentication header containing the challenge.
66-
* @param authChallengePrefix The authentication challenge name.
67-
*
6864
* @return A challenge attributes map.
6965
*/
70-
private static Map<String, String> extractChallengeAttributes(String authenticateHeader,
71-
String authChallengePrefix) {
72-
if (!isBearerChallenge(authenticateHeader, authChallengePrefix)) {
66+
private static Map<String, String> extractChallengeAttributes(String authenticateHeader) {
67+
AuthenticateChallenge bearerChallenge = CoreUtils.parseAuthenticateHeader(authenticateHeader)
68+
.stream()
69+
.filter(challenge -> "Bearer".equalsIgnoreCase(challenge.getScheme()))
70+
.findFirst()
71+
.orElse(null);
72+
73+
if (bearerChallenge == null) {
7374
return Collections.emptyMap();
7475
}
7576

76-
String[] attributes = authenticateHeader.replace("\"", "").substring(authChallengePrefix.length()).split(",");
77-
Map<String, String> attributeMap = new HashMap<>();
78-
79-
for (String pair : attributes) {
80-
// Using trim is ugly, but we need it here because currently the 'claims' attribute comes after two spaces.
81-
String[] keyValue = pair.trim().split("=", 2);
82-
83-
attributeMap.put(keyValue[0], keyValue[1]);
84-
}
85-
86-
return attributeMap;
87-
}
88-
89-
/**
90-
* Verifies whether a challenge is bearer or not.
91-
*
92-
* @param authenticateHeader The authentication header containing all the challenges.
93-
* @param authChallengePrefix The authentication challenge name.
94-
*
95-
* @return A boolean indicating if the challenge is a bearer challenge or not.
96-
*/
97-
private static boolean isBearerChallenge(String authenticateHeader, String authChallengePrefix) {
98-
return (!CoreUtils.isNullOrEmpty(authenticateHeader)
99-
&& authenticateHeader.toLowerCase(Locale.ROOT).startsWith(authChallengePrefix.toLowerCase(Locale.ROOT)));
77+
return bearerChallenge.getParameters();
10078
}
10179

10280
@Override
@@ -154,7 +132,7 @@ public Mono<Boolean> authorizeRequestOnChallenge(HttpPipelineCallContext context
154132

155133
String authority = getRequestAuthority(request);
156134
Map<String, String> challengeAttributes
157-
= extractChallengeAttributes(response.getHeaderValue(WWW_AUTHENTICATE), BEARER_TOKEN_PREFIX);
135+
= extractChallengeAttributes(response.getHeaderValue(WWW_AUTHENTICATE));
158136
String scope = challengeAttributes.get("resource");
159137

160138
if (scope != null) {
@@ -271,8 +249,7 @@ public boolean authorizeRequestOnChallengeSync(HttpPipelineCallContext context,
271249
}
272250

273251
String authority = getRequestAuthority(request);
274-
Map<String, String> challengeAttributes
275-
= extractChallengeAttributes(response.getHeaderValue(WWW_AUTHENTICATE), BEARER_TOKEN_PREFIX);
252+
Map<String, String> challengeAttributes = extractChallengeAttributes(response.getHeaderValue(WWW_AUTHENTICATE));
276253
String scope = challengeAttributes.get("resource");
277254

278255
if (scope != null) {
@@ -436,7 +413,7 @@ && isClaimsPresent(newResponse)
436413

437414
private boolean isClaimsPresent(HttpResponse httpResponse) {
438415
Map<String, String> challengeAttributes
439-
= extractChallengeAttributes(httpResponse.getHeaderValue(WWW_AUTHENTICATE), BEARER_TOKEN_PREFIX);
416+
= extractChallengeAttributes(httpResponse.getHeaderValue(WWW_AUTHENTICATE));
440417

441418
String error = challengeAttributes.get("error");
442419

@@ -517,10 +494,10 @@ private static boolean isChallengeResourceValid(HttpRequest request, String scop
517494
new RuntimeException(String.format("The challenge resource '%s' is not a valid URI.", scope), e));
518495
}
519496

497+
String host = request.getUrl().getHost();
498+
String toEndWith = "." + scopeUri.getHost();
499+
520500
// Returns false if the host specified in the scope does not match the requested domain.
521-
return request.getUrl()
522-
.getHost()
523-
.toLowerCase(Locale.ROOT)
524-
.endsWith("." + scopeUri.getHost().toLowerCase(Locale.ROOT));
501+
return host.regionMatches(true, host.length() - toEndWith.length(), toEndWith, 0, toEndWith.length());
525502
}
526503
}

0 commit comments

Comments
 (0)