This is an automated email from the ASF dual-hosted git repository.
curth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new 825d1d1c0 feat(csharp/src/Drivers/Databricks): Allow configurable auth
scope for client-credentials flow (#2803)
825d1d1c0 is described below
commit 825d1d1c0d06ba0090c5271ef793426b6184a9ab
Author: Todd Meng <[email protected]>
AuthorDate: Fri May 9 10:11:17 2025 -0700
feat(csharp/src/Drivers/Databricks): Allow configurable auth scope for
client-credentials flow (#2803)
Makes auth-scope configurable for client-credentials.
Added a test; verified that it works.
---
.../Auth/OAuthClientCredentialsProvider.cs | 28 +++++++++++++++++++---
.../src/Drivers/Databricks/DatabricksConnection.cs | 2 ++
.../src/Drivers/Databricks/DatabricksParameters.cs | 7 ++++++
csharp/src/Drivers/Databricks/readme.md | 1 +
.../Auth/OAuthClientCredentialsProviderTests.cs | 18 ++++++++++++--
.../Databricks/DatabricksTestConfiguration.cs | 3 +++
.../Databricks/DatabricksTestEnvironment.cs | 4 ++++
7 files changed, 58 insertions(+), 5 deletions(-)
diff --git
a/csharp/src/Drivers/Databricks/Auth/OAuthClientCredentialsProvider.cs
b/csharp/src/Drivers/Databricks/Auth/OAuthClientCredentialsProvider.cs
index 988f9d409..c456c6b71 100644
--- a/csharp/src/Drivers/Databricks/Auth/OAuthClientCredentialsProvider.cs
+++ b/csharp/src/Drivers/Databricks/Auth/OAuthClientCredentialsProvider.cs
@@ -37,6 +37,7 @@ namespace Apache.Arrow.Adbc.Drivers.Databricks.Auth
private readonly string _tokenEndpoint;
private readonly int _timeoutMinutes;
private readonly int _refreshBufferMinutes;
+ private readonly string _scope;
private readonly SemaphoreSlim _tokenLock = new SemaphoreSlim(1, 1);
private TokenInfo? _cachedToken;
@@ -46,6 +47,8 @@ namespace Apache.Arrow.Adbc.Drivers.Databricks.Auth
public DateTime ExpiresAt { get; set; }
private readonly int _refreshBufferMinutes;
+ public string? Scope { get; set; }
+
public TokenInfo(int refreshBufferMinutes)
{
_refreshBufferMinutes = refreshBufferMinutes;
@@ -60,13 +63,15 @@ namespace Apache.Arrow.Adbc.Drivers.Databricks.Auth
/// </summary>
/// <param name="clientId">The OAuth client ID.</param>
/// <param name="clientSecret">The OAuth client secret.</param>
- /// <param name="baseUri">The base URI of the Databricks
workspace.</param>
+ /// <param name="host">The base host of the Databricks
workspace.</param>
+ /// <param name="scope">The scope for the OAuth token.</param>
/// <param name="timeoutMinutes">The timeout in minutes for HTTP
requests.</param>
/// <param name="refreshBufferMinutes">The number of minutes before
token expiration to refresh the token.</param>
public OAuthClientCredentialsProvider(
string clientId,
string clientSecret,
string host,
+ string scope = "sql",
int timeoutMinutes = 1,
int refreshBufferMinutes = 5)
{
@@ -75,6 +80,7 @@ namespace Apache.Arrow.Adbc.Drivers.Databricks.Auth
_host = host ?? throw new ArgumentNullException(nameof(host));
_timeoutMinutes = timeoutMinutes;
_refreshBufferMinutes = refreshBufferMinutes;
+ _scope = scope ?? throw new ArgumentNullException(nameof(scope));
_tokenEndpoint = DetermineTokenEndpoint();
_httpClient = new HttpClient();
@@ -128,7 +134,7 @@ namespace Apache.Arrow.Adbc.Drivers.Databricks.Auth
var requestContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type",
"client_credentials"),
- new KeyValuePair<string, string>("scope", "sql")
+ new KeyValuePair<string, string>("scope", _scope)
});
var request = new HttpRequestMessage(HttpMethod.Post,
_tokenEndpoint)
@@ -172,10 +178,22 @@ namespace Apache.Arrow.Adbc.Drivers.Databricks.Auth
throw new DatabricksException("OAuth expires_in value must be
positive");
}
+ if (!jsonDoc.RootElement.TryGetProperty("scope", out var
scopeElement))
+ {
+ throw new DatabricksException("OAuth response did not contain
scope");
+ }
+
+ string? scope = scopeElement.GetString();
+ if (string.IsNullOrEmpty(scope))
+ {
+ throw new DatabricksException("OAuth scope was null or empty");
+ }
+
return new TokenInfo(_refreshBufferMinutes)
{
AccessToken = accessToken!,
- ExpiresAt = DateTime.UtcNow.AddSeconds(expiresIn)
+ ExpiresAt = DateTime.UtcNow.AddSeconds(expiresIn),
+ Scope = scope!
};
}
@@ -211,5 +229,9 @@ namespace Apache.Arrow.Adbc.Drivers.Databricks.Auth
_httpClient.Dispose();
}
+ public string? GetCachedTokenScope()
+ {
+ return _cachedToken?.Scope;
+ }
}
}
diff --git a/csharp/src/Drivers/Databricks/DatabricksConnection.cs
b/csharp/src/Drivers/Databricks/DatabricksConnection.cs
index 9951b0440..fa62a0c29 100644
--- a/csharp/src/Drivers/Databricks/DatabricksConnection.cs
+++ b/csharp/src/Drivers/Databricks/DatabricksConnection.cs
@@ -194,11 +194,13 @@ namespace Apache.Arrow.Adbc.Drivers.Databricks
Properties.TryGetValue(DatabricksParameters.OAuthClientId, out
string? clientId);
Properties.TryGetValue(DatabricksParameters.OAuthClientSecret,
out string? clientSecret);
+ Properties.TryGetValue(DatabricksParameters.OAuthScope, out
string? scope);
var tokenProvider = new OAuthClientCredentialsProvider(
clientId!,
clientSecret!,
host!,
+ scope: scope ?? "sql",
timeoutMinutes: 1
);
diff --git a/csharp/src/Drivers/Databricks/DatabricksParameters.cs
b/csharp/src/Drivers/Databricks/DatabricksParameters.cs
index 940837b66..8c6122473 100644
--- a/csharp/src/Drivers/Databricks/DatabricksParameters.cs
+++ b/csharp/src/Drivers/Databricks/DatabricksParameters.cs
@@ -141,6 +141,13 @@ namespace Apache.Arrow.Adbc.Drivers.Databricks
/// This is the client secret you obtained when registering your
application with Databricks.
/// </summary>
public const string OAuthClientSecret =
"adbc.databricks.oauth.client_secret";
+
+ /// <summary>
+ /// The OAuth scope for client credentials flow.
+ /// Optional when grant_type is "client_credentials".
+ /// Default value is "sql" if not specified.
+ /// </summary>
+ public const string OAuthScope = "adbc.databricks.oauth.scope";
}
/// <summary>
diff --git a/csharp/src/Drivers/Databricks/readme.md
b/csharp/src/Drivers/Databricks/readme.md
index c47b7a67c..6a0af0c4c 100644
--- a/csharp/src/Drivers/Databricks/readme.md
+++ b/csharp/src/Drivers/Databricks/readme.md
@@ -33,6 +33,7 @@ The Databricks ADBC driver supports the following
authentication methods:
- Set `adbc.databricks.oauth.grant_type` to `client_credentials`
- Set `adbc.databricks.oauth.client_id` to your OAuth client ID
- Set `adbc.databricks.oauth.client_secret` to your OAuth client secret
+ - Set `adbc.databricks.oauth.scope` to your auth scope (defaults to `"sql"`)
- The driver will automatically handle token acquisition, renewal, and
authentication with the Databricks service
Basic (username and password) authentication is not supported at this time.
diff --git
a/csharp/test/Drivers/Databricks/Auth/OAuthClientCredentialsProviderTests.cs
b/csharp/test/Drivers/Databricks/Auth/OAuthClientCredentialsProviderTests.cs
index f8417e221..ec0fbb817 100644
--- a/csharp/test/Drivers/Databricks/Auth/OAuthClientCredentialsProviderTests.cs
+++ b/csharp/test/Drivers/Databricks/Auth/OAuthClientCredentialsProviderTests.cs
@@ -32,7 +32,7 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Databricks.Auth
{
}
- private OAuthClientCredentialsProvider CreateService(int
refreshBufferMinutes = 5)
+ private OAuthClientCredentialsProvider CreateService(int
refreshBufferMinutes = 5, string scope = "sql")
{
string host;
if (!string.IsNullOrEmpty(TestConfiguration.HostName))
@@ -60,7 +60,8 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Databricks.Auth
TestConfiguration.OAuthClientSecret,
host,
timeoutMinutes: 1,
- refreshBufferMinutes: refreshBufferMinutes);
+ refreshBufferMinutes: refreshBufferMinutes,
+ scope: scope);
}
[SkippableFact]
@@ -120,5 +121,18 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Databricks.Auth
// Tokens should be different since we're forcing a refresh
Assert.NotEqual(token1, token2);
}
+
+ [SkippableFact]
+ public async Task GetAccessToken_WithCustomScope_ReturnsToken()
+ {
+ Skip.IfNot(!string.IsNullOrEmpty(TestConfiguration.OAuthClientId),
"OAuth credentials not configured");
+ String scope = "all-apis";
+ var service = CreateService(scope: scope);
+ var token = await service.GetAccessTokenAsync();
+
+ Assert.NotNull(token);
+ Assert.NotEmpty(token);
+ Assert.Equal(scope, service.GetCachedTokenScope());
+ }
}
}
diff --git a/csharp/test/Drivers/Databricks/DatabricksTestConfiguration.cs
b/csharp/test/Drivers/Databricks/DatabricksTestConfiguration.cs
index 5690604fc..05c3e4a11 100644
--- a/csharp/test/Drivers/Databricks/DatabricksTestConfiguration.cs
+++ b/csharp/test/Drivers/Databricks/DatabricksTestConfiguration.cs
@@ -30,5 +30,8 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Databricks
[JsonPropertyName("client_secret"), JsonIgnore(Condition =
JsonIgnoreCondition.WhenWritingDefault)]
public string OAuthClientSecret { get; set; } = string.Empty;
+
+ [JsonPropertyName("scope"), JsonIgnore(Condition =
JsonIgnoreCondition.WhenWritingDefault)]
+ public string OAuthScope { get; set; } = string.Empty;
}
}
diff --git a/csharp/test/Drivers/Databricks/DatabricksTestEnvironment.cs
b/csharp/test/Drivers/Databricks/DatabricksTestEnvironment.cs
index 0a11556fd..d44f19645 100644
--- a/csharp/test/Drivers/Databricks/DatabricksTestEnvironment.cs
+++ b/csharp/test/Drivers/Databricks/DatabricksTestEnvironment.cs
@@ -105,6 +105,10 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Databricks
{
parameters.Add(DatabricksParameters.OAuthClientSecret,
testConfiguration.OAuthClientSecret!);
}
+ if (!string.IsNullOrEmpty(testConfiguration.OAuthScope))
+ {
+ parameters.Add(DatabricksParameters.OAuthScope,
testConfiguration.OAuthScope!);
+ }
if (!string.IsNullOrEmpty(testConfiguration.Type))
{
parameters.Add(SparkParameters.Type, testConfiguration.Type!);