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!);

Reply via email to