Skip to content

Commit 8f5da08

Browse files
Validate access_token and token_type in OAuth token responses
When a token endpoint returns 200 with no access_token or token_type, the SDK would NPE in the Token constructor. This adds explicit validation in both requestToken() and retrieveToken() that throws DatabricksException with the endpoint URL for debuggability. Co-authored-by: Isaac
1 parent 958f1d9 commit 8f5da08

3 files changed

Lines changed: 94 additions & 0 deletions

File tree

databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/TokenEndpointClient.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ public static OAuthResponse requestToken(
9292
String.format(
9393
"Token request failed with error: %s - %s", response.getErrorCode(), errorSummary));
9494
}
95+
if (response.getAccessToken() == null || response.getAccessToken().isEmpty()) {
96+
throw new DatabricksException(
97+
String.format(
98+
"Token request to %s returned a response with no access_token", tokenEndpointUrl));
99+
}
100+
if (response.getTokenType() == null || response.getTokenType().isEmpty()) {
101+
throw new DatabricksException(
102+
String.format(
103+
"Token request to %s returned a response with no token_type", tokenEndpointUrl));
104+
}
95105
LOG.debug("Successfully obtained token response from {}", tokenEndpointUrl);
96106
return response;
97107
}
@@ -143,6 +153,15 @@ public static Token retrieveToken(
143153
if (resp.getErrorCode() != null) {
144154
throw new IllegalArgumentException(resp.getErrorCode() + ": " + resp.getErrorSummary());
145155
}
156+
if (resp.getAccessToken() == null || resp.getAccessToken().isEmpty()) {
157+
throw new DatabricksException(
158+
String.format(
159+
"Token request to %s returned a response with no access_token", tokenUrl));
160+
}
161+
if (resp.getTokenType() == null || resp.getTokenType().isEmpty()) {
162+
throw new DatabricksException(
163+
String.format("Token request to %s returned a response with no token_type", tokenUrl));
164+
}
146165
Instant expiry = Instant.now().plusSeconds(resp.getExpiresIn());
147166
return new Token(resp.getAccessToken(), resp.getTokenType(), resp.getRefreshToken(), expiry);
148167
} catch (Exception e) {

databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/EndpointTokenSourceTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ private static Stream<Arguments> provideEndpointTokenScenarios() throws Exceptio
4545
+ "\"error\":\"invalid_client\","
4646
+ "\"error_description\":\"Client authentication failed\"}";
4747

48+
String noAccessTokenJson = "{" + "\"token_type\":\"Bearer\"," + "\"expires_in\":3600" + "}";
49+
4850
String malformedJson = "{not valid json}";
4951

5052
// Mock DatabricksOAuthTokenSource for control plane token
@@ -69,6 +71,12 @@ private static Stream<Arguments> provideEndpointTokenScenarios() throws Exceptio
6971
.thenReturn(
7072
new Response(malformedJson, 200, "OK", new URL("https://test.databricks.com/")));
7173

74+
// Mock HttpClient for no access_token
75+
HttpClient mockNoAccessTokenClient = mock(HttpClient.class);
76+
when(mockNoAccessTokenClient.execute(any()))
77+
.thenReturn(
78+
new Response(noAccessTokenJson, 200, "OK", new URL("https://test.databricks.com/")));
79+
7280
// Mock HttpClient for IOException
7381
HttpClient mockIOExceptionClient = mock(HttpClient.class);
7482
when(mockIOExceptionClient.execute(any())).thenThrow(new IOException("Network error"));
@@ -183,6 +191,17 @@ private static Stream<Arguments> provideEndpointTokenScenarios() throws Exceptio
183191
null,
184192
null,
185193
null,
194+
0),
195+
Arguments.of(
196+
"Missing access_token in response",
197+
mockCpTokenSource,
198+
TEST_AUTH_DETAILS,
199+
mockNoAccessTokenClient,
200+
TEST_HOST,
201+
DatabricksException.class,
202+
null,
203+
null,
204+
null,
186205
0));
187206
}
188207

databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenEndpointClientTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ private static Stream<Arguments> provideTokenScenarios() throws Exception {
3434
"{"
3535
+ "\"error\":\"invalid_client\","
3636
+ "\"error_description\":\"Client authentication failed\"}";
37+
// Response with no access_token
38+
String noAccessTokenJson = "{" + "\"token_type\":\"Bearer\"," + "\"expires_in\":3600" + "}";
39+
// Response with no token_type
40+
String noTokenTypeJson =
41+
"{" + "\"access_token\":\"test-access-token\"," + "\"expires_in\":3600" + "}";
42+
// Response with empty access_token
43+
String emptyAccessTokenJson =
44+
"{" + "\"access_token\":\"\"," + "\"token_type\":\"Bearer\"," + "\"expires_in\":3600" + "}";
3745
// Malformed JSON
3846
String malformedJson = "{not valid json}";
3947

@@ -54,6 +62,24 @@ private static Stream<Arguments> provideTokenScenarios() throws Exception {
5462
.thenReturn(
5563
new Response(malformedJson, 200, "OK", new URL("https://test.databricks.com/")));
5664

65+
// Mock HttpClient for no access_token
66+
HttpClient mockNoAccessTokenClient = mock(HttpClient.class);
67+
when(mockNoAccessTokenClient.execute(any(FormRequest.class)))
68+
.thenReturn(
69+
new Response(noAccessTokenJson, 200, "OK", new URL("https://test.databricks.com/")));
70+
71+
// Mock HttpClient for no token_type
72+
HttpClient mockNoTokenTypeClient = mock(HttpClient.class);
73+
when(mockNoTokenTypeClient.execute(any(FormRequest.class)))
74+
.thenReturn(
75+
new Response(noTokenTypeJson, 200, "OK", new URL("https://test.databricks.com/")));
76+
77+
// Mock HttpClient for empty access_token
78+
HttpClient mockEmptyAccessTokenClient = mock(HttpClient.class);
79+
when(mockEmptyAccessTokenClient.execute(any(FormRequest.class)))
80+
.thenReturn(
81+
new Response(emptyAccessTokenJson, 200, "OK", new URL("https://test.databricks.com/")));
82+
5783
// Mock HttpClient for IOException
5884
HttpClient mockIOExceptionClient = mock(HttpClient.class);
5985
when(mockIOExceptionClient.execute(any(FormRequest.class)))
@@ -139,6 +165,36 @@ private static Stream<Arguments> provideTokenScenarios() throws Exception {
139165
null,
140166
null,
141167
0,
168+
null),
169+
Arguments.of(
170+
"Missing access_token in response",
171+
mockNoAccessTokenClient,
172+
TOKEN_ENDPOINT_URL,
173+
PARAMS,
174+
DatabricksException.class,
175+
null,
176+
null,
177+
0,
178+
null),
179+
Arguments.of(
180+
"Missing token_type in response",
181+
mockNoTokenTypeClient,
182+
TOKEN_ENDPOINT_URL,
183+
PARAMS,
184+
DatabricksException.class,
185+
null,
186+
null,
187+
0,
188+
null),
189+
Arguments.of(
190+
"Empty access_token in response",
191+
mockEmptyAccessTokenClient,
192+
TOKEN_ENDPOINT_URL,
193+
PARAMS,
194+
DatabricksException.class,
195+
null,
196+
null,
197+
0,
142198
null));
143199
}
144200

0 commit comments

Comments
 (0)