Skip to content

Commit 2e2bd35

Browse files
improve comments, refcator tests and remove unused field
1 parent b19225a commit 2e2bd35

3 files changed

Lines changed: 41 additions & 44 deletions

File tree

databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksCliCredentialsProvider.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public OAuthHeaderFactory configure(DatabricksConfig config) {
123123
@Override
124124
public Token getToken() {
125125
Token t = tokenSource.getToken();
126-
validateTokenScopes(t, scopes, host);
126+
validateTokenScopes(t, scopes);
127127
return t;
128128
}
129129
};
@@ -171,7 +171,7 @@ public Token getToken() {
171171
* will silently use the wrong scopes. This check surfaces that mismatch early with an actionable
172172
* error telling the user how to re-authenticate with the correct scopes.
173173
*/
174-
static void validateTokenScopes(Token token, List<String> requestedScopes, String host) {
174+
static void validateTokenScopes(Token token, List<String> requestedScopes) {
175175
Map<String, Object> claims = getJwtClaims(token.getAccessToken());
176176
if (claims == null) {
177177
LOG.debug("Could not decode token as JWT to validate scopes");
@@ -204,15 +204,16 @@ static void validateTokenScopes(Token token, List<String> requestedScopes, Strin
204204
String.format(
205205
"Token issued by Databricks CLI has scopes %s which do not match "
206206
+ "the configured scopes %s. Please re-authenticate "
207-
+ "with the desired scopes by running `databricks auth login` with the --scopes flag."
207+
+ "with the desired scopes by running `databricks auth login` with the --scopes flag. "
208208
+ "Scopes default to all-apis.",
209209
sortedTokenScopes, sortedRequested));
210210
}
211211
}
212212

213213
/**
214214
* Decode a JWT access token and return its payload claims. Returns null if the token is not a
215-
* valid JWT.
215+
* valid JWT. No signature verification is performed — the token was already authenticated by the
216+
* CLI, and we only need to read the scope claim for comparison.
216217
*/
217218
private static Map<String, Object> getJwtClaims(String accessToken) {
218219
String[] parts = accessToken.split("\\.");
@@ -233,18 +234,12 @@ private static Map<String, Object> getJwtClaims(String accessToken) {
233234
}
234235

235236
/**
236-
* Parse the JWT "scope" claim, which can be either a space-delimited string or a JSON array.
237-
* Returns null if the type is unexpected.
237+
* Parse the JWT "scope" claim. Per RFC 9068, this is a space-delimited string. Returns null if
238+
* the type is unexpected.
238239
*/
239240
private static Set<String> parseScopeClaim(Object scopeClaim) {
240241
if (scopeClaim instanceof String) {
241242
return new HashSet<>(Arrays.asList(((String) scopeClaim).split("\\s+")));
242-
} else if (scopeClaim instanceof List) {
243-
Set<String> scopes = new HashSet<>();
244-
for (Object s : (List<?>) scopeClaim) {
245-
scopes.add(String.valueOf(s));
246-
}
247-
return scopes;
248243
}
249244
return null;
250245
}

databricks-sdk-java/src/test/java/com/databricks/sdk/core/DatabricksCliCredentialsProviderTest.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,4 @@ void testBuildHostArgs_UnifiedHostFalse_WithAccountHost() {
139139
CLI_PATH, "auth", "token", "--host", ACCOUNT_HOST, "--account-id", ACCOUNT_ID),
140140
cmd);
141141
}
142-
143-
@Test
144-
void testScopesExplicitlySetFlag() {
145-
DatabricksConfig config = new DatabricksConfig();
146-
assertFalse(config.isScopesExplicitlySet());
147-
148-
config.setScopes(Arrays.asList("sql", "clusters"));
149-
assertTrue(config.isScopesExplicitlySet());
150-
}
151142
}

databricks-sdk-java/src/test/java/com/databricks/sdk/core/DatabricksCliScopeValidationTest.java

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
class DatabricksCliScopeValidationTest {
1616

17-
private static final String HOST = "https://my-workspace.cloud.databricks.com";
1817
private static final ObjectMapper MAPPER = new ObjectMapper();
1918

2019
/** Builds a fake JWT (header.payload.signature) with the given claims. */
@@ -62,16 +61,24 @@ static List<Arguments> scopeValidationCases() {
6261
Arrays.asList("all-apis", "offline_access"),
6362
false,
6463
"offline_access_in_config_only"),
65-
// Scope claim as list instead of string.
64+
// Order should not matter.
6665
Arguments.of(
67-
new HashMap<String, Object>() {
68-
{
69-
put("scope", Arrays.asList("sql", "offline_access"));
70-
}
71-
},
66+
Collections.singletonMap("scope", "clusters sql"),
67+
Arrays.asList("sql", "clusters"),
68+
false,
69+
"multiple_scopes_order_independent"),
70+
// Partial overlap is still a mismatch.
71+
Arguments.of(
72+
Collections.singletonMap("scope", "sql clusters"),
73+
Arrays.asList("sql", "compute"),
74+
true,
75+
"multiple_scopes_partial_overlap_mismatch"),
76+
// No scope claim in token — validation is skipped.
77+
Arguments.of(
78+
Collections.singletonMap("sub", "user@example.com"),
7279
Collections.singletonList("sql"),
7380
false,
74-
"scope_as_list"));
81+
"no_scope_claim_skips_validation"));
7582
}
7683

7784
@ParameterizedTest(name = "{3}")
@@ -86,31 +93,20 @@ void testScopeValidation(
8693
if (expectError) {
8794
assertThrows(
8895
DatabricksCliCredentialsProvider.ScopeMismatchException.class,
89-
() ->
90-
DatabricksCliCredentialsProvider.validateTokenScopes(token, configuredScopes, HOST));
96+
() -> DatabricksCliCredentialsProvider.validateTokenScopes(token, configuredScopes));
9197
} else {
9298
assertDoesNotThrow(
93-
() ->
94-
DatabricksCliCredentialsProvider.validateTokenScopes(token, configuredScopes, HOST));
99+
() -> DatabricksCliCredentialsProvider.validateTokenScopes(token, configuredScopes));
95100
}
96101
}
97102

98-
@Test
99-
void testNoScopeClaimSkipsValidation() {
100-
Token token = makeToken(Collections.singletonMap("sub", "user@example.com"));
101-
assertDoesNotThrow(
102-
() ->
103-
DatabricksCliCredentialsProvider.validateTokenScopes(
104-
token, Collections.singletonList("sql"), HOST));
105-
}
106-
107103
@Test
108104
void testNonJwtTokenSkipsValidation() {
109105
Token token = new Token("opaque-token-string", "Bearer", Instant.now().plusSeconds(3600));
110106
assertDoesNotThrow(
111107
() ->
112108
DatabricksCliCredentialsProvider.validateTokenScopes(
113-
token, Collections.singletonList("sql"), HOST));
109+
token, Collections.singletonList("sql")));
114110
}
115111

116112
@Test
@@ -121,12 +117,27 @@ void testErrorMessageContainsReauthCommand() {
121117
DatabricksCliCredentialsProvider.ScopeMismatchException.class,
122118
() ->
123119
DatabricksCliCredentialsProvider.validateTokenScopes(
124-
token, Arrays.asList("sql", "offline_access"), HOST));
120+
token, Arrays.asList("sql", "offline_access")));
125121
assertTrue(
126122
e.getMessage().contains("databricks auth login"),
127123
"Expected re-auth command in error message, got: " + e.getMessage());
128124
assertTrue(
129125
e.getMessage().contains("do not match the configured scopes"),
130126
"Expected scope mismatch details in error message, got: " + e.getMessage());
131127
}
128+
129+
@Test
130+
void testScopesExplicitlySetFlag() {
131+
DatabricksConfig config = new DatabricksConfig();
132+
assertFalse(config.isScopesExplicitlySet());
133+
134+
config.setScopes(Arrays.asList("sql", "clusters"));
135+
assertTrue(config.isScopesExplicitlySet());
136+
137+
config.setScopes(Collections.emptyList());
138+
assertFalse(config.isScopesExplicitlySet(), "Empty list should not count as explicitly set");
139+
140+
config.setScopes(null);
141+
assertFalse(config.isScopesExplicitlySet(), "null should not count as explicitly set");
142+
}
132143
}

0 commit comments

Comments
 (0)