From 75314eb4c877b9bd9e94a90c18bee520d1c993a9 Mon Sep 17 00:00:00 2001 From: Mubashir Kazia Date: Mon, 4 May 2026 16:09:35 -0400 Subject: [PATCH] Canonicalize OAuth Bearer scheme when building Authorization header Identity providers may return token_type in any case (e.g. "bearer", "BEARER") per RFC 6749/6750, but some downstream servers and proxies reject anything other than the canonical "Bearer". Add Token.getCanonicalTokenType() and route the three Authorization-header construction sites (OAuthHeaderFactory, AzureCliCredentialsProvider, ServingEndpointsDataPlaneImpl) through it. Non-Bearer schemes pass through unchanged. Co-authored-by: Isaac --- .../sdk/core/AzureCliCredentialsProvider.java | 3 ++- .../sdk/core/oauth/OAuthHeaderFactory.java | 3 ++- .../com/databricks/sdk/core/oauth/Token.java | 16 ++++++++++++++++ .../serving/ServingEndpointsDataPlaneImpl.java | 2 +- .../sdk/core/oauth/OAuthHeaderFactoryTest.java | 14 +++++++++++++- .../com/databricks/sdk/core/oauth/TokenTest.java | 16 ++++++++++++++++ 6 files changed, 50 insertions(+), 4 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/AzureCliCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/AzureCliCredentialsProvider.java index 1c97a7ac9..77f9c9fdb 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/AzureCliCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/AzureCliCredentialsProvider.java @@ -99,7 +99,8 @@ public OAuthHeaderFactory configure(DatabricksConfig config) { () -> { Token token = tokenSource.getToken(); Map headers = new HashMap<>(); - headers.put("Authorization", token.getTokenType() + " " + token.getAccessToken()); + headers.put( + "Authorization", token.getCanonicalTokenType() + " " + token.getAccessToken()); if (finalMgmtTokenSource != null) { AzureUtils.addSpManagementToken(finalMgmtTokenSource, headers); } diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthHeaderFactory.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthHeaderFactory.java index 614614c55..94580ff12 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthHeaderFactory.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthHeaderFactory.java @@ -51,7 +51,8 @@ public Token getToken() { public Map headers() { Token token = tokenSource.getToken(); Map headers = new HashMap<>(); - headers.put("Authorization", token.getTokenType() + " " + token.getAccessToken()); + headers.put( + "Authorization", token.getCanonicalTokenType() + " " + token.getAccessToken()); return headers; } }; diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Token.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Token.java index 4a3b42a7e..cafc66587 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Token.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Token.java @@ -51,6 +51,22 @@ public String getTokenType() { return tokenType; } + /** + * Returns the token type canonicalized for use as the Authorization header scheme. Per RFC 6749 + * §5.1 / RFC 6750 §2.1, identity providers may return {@code token_type} in any case (e.g. + * "bearer", "BEARER"). Some downstream servers and proxies reject anything other than the + * canonical "Bearer" capitalization, so we normalize that scheme here. Other schemes are returned + * unchanged. + * + * @return the canonicalized token type + */ + public String getCanonicalTokenType() { + if ("bearer".equalsIgnoreCase(tokenType)) { + return "Bearer"; + } + return tokenType; + } + /** * Returns the refresh token, if available. May be null for non-refreshable tokens. * diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/service/serving/ServingEndpointsDataPlaneImpl.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/service/serving/ServingEndpointsDataPlaneImpl.java index 5410d88d7..9c4806440 100755 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/service/serving/ServingEndpointsDataPlaneImpl.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/service/serving/ServingEndpointsDataPlaneImpl.java @@ -68,7 +68,7 @@ public QueryEndpointResponse query(QueryEndpointInput request) { } RequestOptions options = new RequestOptions() - .withAuthorization(token.getTokenType() + " " + token.getAccessToken()) + .withAuthorization(token.getCanonicalTokenType() + " " + token.getAccessToken()) .withUrl(path); return apiClient.execute(req, QueryEndpointResponse.class, options); diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/OAuthHeaderFactoryTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/OAuthHeaderFactoryTest.java index f0b83153c..41b936e79 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/OAuthHeaderFactoryTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/OAuthHeaderFactoryTest.java @@ -36,7 +36,19 @@ private static Stream provideTokenSourceTestCases() { Arguments.of( "Token with custom type", new Token(TOKEN_VALUE, "Custom", expiry), - Collections.singletonMap("Authorization", "Custom " + TOKEN_VALUE))); + Collections.singletonMap("Authorization", "Custom " + TOKEN_VALUE)), + Arguments.of( + "Lowercase bearer is canonicalized", + new Token(TOKEN_VALUE, "bearer", expiry), + Collections.singletonMap("Authorization", "Bearer " + TOKEN_VALUE)), + Arguments.of( + "Uppercase BEARER is canonicalized", + new Token(TOKEN_VALUE, "BEARER", expiry), + Collections.singletonMap("Authorization", "Bearer " + TOKEN_VALUE)), + Arguments.of( + "Mixed-case BeArEr is canonicalized", + new Token(TOKEN_VALUE, "BeArEr", expiry), + Collections.singletonMap("Authorization", "Bearer " + TOKEN_VALUE))); } @ParameterizedTest(name = "{0}") diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenTest.java index a0173cbf8..454e7bbda 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenTest.java @@ -29,4 +29,20 @@ void createRefreshableToken() { assertEquals(refreshToken, token.getRefreshToken()); assertEquals(currentInstant.plusSeconds(300), token.getExpiry()); } + + @Test + void canonicalTokenTypeNormalizesBearerCasing() { + Instant expiry = currentInstant.plusSeconds(300); + assertEquals("Bearer", new Token(accessToken, "Bearer", expiry).getCanonicalTokenType()); + assertEquals("Bearer", new Token(accessToken, "bearer", expiry).getCanonicalTokenType()); + assertEquals("Bearer", new Token(accessToken, "BEARER", expiry).getCanonicalTokenType()); + assertEquals("Bearer", new Token(accessToken, "BeArEr", expiry).getCanonicalTokenType()); + } + + @Test + void canonicalTokenTypePreservesNonBearerSchemes() { + Instant expiry = currentInstant.plusSeconds(300); + assertEquals("Custom", new Token(accessToken, "Custom", expiry).getCanonicalTokenType()); + assertEquals("MAC", new Token(accessToken, "MAC", expiry).getCanonicalTokenType()); + } }