From 71a3035303f15c976c9b1db2070d2686bb4c4e06 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 7 Aug 2025 12:42:05 +0000 Subject: [PATCH 1/5] Propagate user scopes --- .../databricks/sdk/core/DatabricksConfig.java | 9 ++++++++ .../sdk/core/DefaultCredentialsProvider.java | 3 ++- .../oauth/DatabricksOAuthTokenSource.java | 23 +++++++++++++++++-- .../ExternalBrowserCredentialsProvider.java | 16 ++++++++++++- .../oauth/GithubOidcCredentialsProvider.java | 3 +-- .../sdk/core/oauth/OAuthClient.java | 12 +--------- ...2MServicePrincipalCredentialsProvider.java | 3 +-- .../oauth/DatabricksOAuthTokenSourceTest.java | 1 + ...xternalBrowserCredentialsProviderTest.java | 2 +- 9 files changed, 52 insertions(+), 20 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java index 0bc0b868a..da0012c3b 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java @@ -351,7 +351,16 @@ public DatabricksConfig setOAuthRedirectUrl(String redirectUrl) { return this; } + /** + * Returns the scopes to use for the current configuration. If no scopes were provided, + * returns the default "all-apis" scope. + * + * @return The scopes to use for the current configuration + */ public List getScopes() { + if (scopes == null || scopes.isEmpty()) { + return Arrays.asList("all-apis"); + } return scopes; } diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java index f72aa435b..6d53d22f6 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java @@ -139,6 +139,7 @@ private void addOIDCCredentialsProviders(DatabricksConfig config) { config.getHttpClient()) .audience(config.getTokenAudience()) .accountId(config.isAccountClient() ? config.getAccountId() : null) + .scopes(config.getScopes()) .build(); providers.add(new TokenSourceCredentialsProvider(oauthTokenSource, namedIdTokenSource.name)); @@ -157,7 +158,7 @@ private synchronized void addDefaultCredentialsProviders(DatabricksConfig config } providers.add(new PatCredentialsProvider()); - providers.add(new BasicCredentialsProvider()); + providers.add(new BasicCredentialsProvider()); providers.add(new OAuthM2MServicePrincipalCredentialsProvider()); // Add OIDC-based providers diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java index 627f238a5..af822e37d 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java @@ -4,7 +4,9 @@ import com.databricks.sdk.core.http.HttpClient; import com.google.common.base.Strings; import java.time.Instant; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import org.slf4j.Logger; @@ -31,10 +33,11 @@ public class DatabricksOAuthTokenSource implements TokenSource { private final IDTokenSource idTokenSource; /** HTTP client for making token exchange requests. */ private final HttpClient httpClient; + /** Scopes to request during token exchange. */ + private final List scopes; private static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"; private static final String SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt"; - private static final String SCOPE = "all-apis"; private static final String GRANT_TYPE_PARAM = "grant_type"; private static final String SUBJECT_TOKEN_PARAM = "subject_token"; private static final String SUBJECT_TOKEN_TYPE_PARAM = "subject_token_type"; @@ -49,6 +52,7 @@ private DatabricksOAuthTokenSource(Builder builder) { this.audience = builder.audience; this.idTokenSource = builder.idTokenSource; this.httpClient = builder.httpClient; + this.scopes = builder.scopes == null ? Arrays.asList() : builder.scopes; } /** @@ -63,6 +67,7 @@ public static class Builder { private final HttpClient httpClient; private String accountId; private String audience; + private List scopes; /** * Creates a new Builder with required parameters. @@ -108,6 +113,17 @@ public Builder audience(String audience) { return this; } + /** + * Sets the scopes to request during token exchange. + * + * @param scopes The scopes to request. + * @return This builder instance. + */ + public Builder scopes(List scopes) { + this.scopes = scopes; + return this; + } + /** * Builds a new DatabricksOAuthTokenSource instance. * @@ -149,7 +165,10 @@ public Token getToken() { params.put(GRANT_TYPE_PARAM, GRANT_TYPE); params.put(SUBJECT_TOKEN_PARAM, idToken.getValue()); params.put(SUBJECT_TOKEN_TYPE_PARAM, SUBJECT_TOKEN_TYPE); - params.put(SCOPE_PARAM, SCOPE); + if (scopes == null) { + throw new IllegalArgumentException("Scopes cannot be null. Use .scopes() method on the builder to set scopes."); + } + params.put(SCOPE_PARAM, String.join(" ", scopes)); params.put(CLIENT_ID_PARAM, clientId); OAuthResponse response; diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java index 70b7e1e21..ded5f1f0f 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java @@ -5,8 +5,11 @@ import com.databricks.sdk.core.DatabricksException; import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; import java.util.Objects; import java.util.Optional; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -106,6 +109,17 @@ CachedTokenSource performBrowserAuth( DatabricksConfig config, String clientId, String clientSecret, TokenCache tokenCache) throws IOException { LOGGER.debug("Performing browser authentication"); + + // Get user-provided scopes and add required default scopes. + Set scopes = new HashSet<>(config.getScopes()); + + // Needed to request a refresh token. + scopes.add("offline_access"); + + if (config.isAzure()) { + scopes.add(config.getEffectiveAzureLoginAppId() + "/user_impersonation"); + } + OAuthClient client = new OAuthClient.Builder() .withHttpClient(config.getHttpClient()) @@ -114,7 +128,7 @@ CachedTokenSource performBrowserAuth( .withHost(config.getHost()) .withAccountId(config.getAccountId()) .withRedirectUrl(config.getEffectiveOAuthRedirectUrl()) - .withScopes(config.getScopes()) + .withScopes(new ArrayList<>(scopes)) .build(); Consent consent = client.initiateConsent(); diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/GithubOidcCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/GithubOidcCredentialsProvider.java index 3b1294797..c52fcf09d 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/GithubOidcCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/GithubOidcCredentialsProvider.java @@ -6,7 +6,6 @@ import com.databricks.sdk.core.HeaderFactory; import com.google.common.collect.ImmutableMap; import java.io.IOException; -import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -47,7 +46,7 @@ public HeaderFactory configure(DatabricksConfig config) throws DatabricksExcepti .withHttpClient(config.getHttpClient()) .withClientId(config.getClientId()) .withTokenUrl(endpointUrl) - .withScopes(Collections.singletonList("all-apis")) + .withScopes(config.getScopes()) .withAuthParameterPosition(AuthParameterPosition.HEADER) .withEndpointParametersSupplier( () -> diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthClient.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthClient.java index 139031c8c..25df0c0a2 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthClient.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthClient.java @@ -109,17 +109,7 @@ private OAuthClient(Builder b) throws IOException { this.isAzure = config.isAzure(); this.tokenUrl = oidc.getTokenEndpoint(); this.authUrl = oidc.getAuthorizationEndpoint(); - - List scopes = b.scopes; - if (scopes == null) { - scopes = Arrays.asList("offline_access", "clusters", "sql"); - } - if (config.isAzure()) { - scopes = - Arrays.asList( - config.getEffectiveAzureLoginAppId() + "/user_impersonation", "offline_access"); - } - this.scopes = scopes; + this.scopes = b.scopes; } public String getHost() { diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthM2MServicePrincipalCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthM2MServicePrincipalCredentialsProvider.java index fff6e351c..bb97f238b 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthM2MServicePrincipalCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OAuthM2MServicePrincipalCredentialsProvider.java @@ -2,7 +2,6 @@ import com.databricks.sdk.core.*; import java.io.IOException; -import java.util.Collections; /** * Adds refreshed Databricks machine-to-machine OAuth Bearer token to every request, if @@ -33,7 +32,7 @@ public OAuthHeaderFactory configure(DatabricksConfig config) { .withClientId(config.getClientId()) .withClientSecret(config.getClientSecret()) .withTokenUrl(jsonResponse.getTokenEndpoint()) - .withScopes(Collections.singletonList("all-apis")) + .withScopes(config.getScopes()) .withAuthParameterPosition(AuthParameterPosition.HEADER) .build(); diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSourceTest.java index ee226cd42..ed2cc3bdf 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSourceTest.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProviderTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProviderTest.java index 968cde75b..ec94f161c 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProviderTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProviderTest.java @@ -62,7 +62,7 @@ void clientAndConsentTest() throws IOException { assertTrue(authUrl.contains("response_type=code")); assertTrue(authUrl.contains("client_id=test-client-id")); assertTrue(authUrl.contains("redirect_uri=http://localhost:8080/callback")); - assertTrue(authUrl.contains("scope=offline_access%20clusters%20sql")); + assertTrue(authUrl.contains("scope=all-apis")); } } From 570c1af8b8724b31441c680d1e7e2a4f131e99ec Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 7 Aug 2025 12:45:24 +0000 Subject: [PATCH 2/5] Add changelog --- NEXT_CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 5f3d0433c..8aaa318fb 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -6,6 +6,8 @@ ### Bug Fixes +- User provided scopes are now properly propagated in OAuth flows. + ### Documentation ### Internal Changes From 06e20365e09b71ff52c1350ee7daf386e8f6c2c8 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 7 Aug 2025 12:47:59 +0000 Subject: [PATCH 3/5] Format --- .../main/java/com/databricks/sdk/core/DatabricksConfig.java | 6 +++--- .../com/databricks/sdk/core/DefaultCredentialsProvider.java | 2 +- .../sdk/core/oauth/DatabricksOAuthTokenSource.java | 3 --- .../sdk/core/oauth/ExternalBrowserCredentialsProvider.java | 4 ++-- .../sdk/core/oauth/DatabricksOAuthTokenSourceTest.java | 1 - 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java index da0012c3b..9d6af5ada 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java @@ -352,9 +352,9 @@ public DatabricksConfig setOAuthRedirectUrl(String redirectUrl) { } /** - * Returns the scopes to use for the current configuration. If no scopes were provided, - * returns the default "all-apis" scope. - * + * Returns the scopes to use for the current configuration. If no scopes were provided, returns + * the default "all-apis" scope. + * * @return The scopes to use for the current configuration */ public List getScopes() { diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java index 6d53d22f6..5231a8afc 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java @@ -158,7 +158,7 @@ private synchronized void addDefaultCredentialsProviders(DatabricksConfig config } providers.add(new PatCredentialsProvider()); - providers.add(new BasicCredentialsProvider()); + providers.add(new BasicCredentialsProvider()); providers.add(new OAuthM2MServicePrincipalCredentialsProvider()); // Add OIDC-based providers diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java index af822e37d..026197e12 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java @@ -165,9 +165,6 @@ public Token getToken() { params.put(GRANT_TYPE_PARAM, GRANT_TYPE); params.put(SUBJECT_TOKEN_PARAM, idToken.getValue()); params.put(SUBJECT_TOKEN_TYPE_PARAM, SUBJECT_TOKEN_TYPE); - if (scopes == null) { - throw new IllegalArgumentException("Scopes cannot be null. Use .scopes() method on the builder to set scopes."); - } params.put(SCOPE_PARAM, String.join(" ", scopes)); params.put(CLIENT_ID_PARAM, clientId); diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java index ded5f1f0f..019dbf6bc 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java @@ -109,7 +109,7 @@ CachedTokenSource performBrowserAuth( DatabricksConfig config, String clientId, String clientSecret, TokenCache tokenCache) throws IOException { LOGGER.debug("Performing browser authentication"); - + // Get user-provided scopes and add required default scopes. Set scopes = new HashSet<>(config.getScopes()); @@ -119,7 +119,7 @@ CachedTokenSource performBrowserAuth( if (config.isAzure()) { scopes.add(config.getEffectiveAzureLoginAppId() + "/user_impersonation"); } - + OAuthClient client = new OAuthClient.Builder() .withHttpClient(config.getHttpClient()) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSourceTest.java index ed2cc3bdf..ee226cd42 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSourceTest.java @@ -12,7 +12,6 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; From 64617a97b25a34881667e349fe313819fbd4a6c7 Mon Sep 17 00:00:00 2001 From: Renaud Hartert Date: Thu, 7 Aug 2025 15:57:25 +0200 Subject: [PATCH 4/5] Update NEXT_CHANGELOG.md --- NEXT_CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 8aaa318fb..a2b2b822e 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -6,7 +6,7 @@ ### Bug Fixes -- User provided scopes are now properly propagated in OAuth flows. +* User provided scopes are now properly propagated in OAuth flows. ### Documentation From 6528a5a6edac59d8bd923c9a132dd82182d8c464 Mon Sep 17 00:00:00 2001 From: Renaud Hartert Date: Thu, 7 Aug 2025 16:02:25 +0200 Subject: [PATCH 5/5] Update NEXT_CHANGELOG.md --- NEXT_CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index a2b2b822e..b60768575 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -7,6 +7,7 @@ ### Bug Fixes * User provided scopes are now properly propagated in OAuth flows. +* [Warning] Correctly defaults to scope `all-apis` (instead of `clusters sql`) in U2M if no scopes are provided by the users. This change aligns the Java SDK logic with the Python and Go SDK logic. ### Documentation