You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
3-task breakdown covering foundation + HTTP client changes,
M2M provider, and U2M provider with full test coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
**Sprint goal:** Implement complete OAuth 2.0 authentication (U2M + M2M) for the Rust ADBC driver as described in the [OAuth design doc](oauth-u2m-m2m-design.md).
5
+
6
+
---
7
+
8
+
## Story
9
+
10
+
**Title:** Implement OAuth U2M and M2M authentication
11
+
12
+
**Description:**
13
+
Add OAuth 2.0 support to the Rust ADBC driver, covering:
14
+
- M2M (Client Credentials) flow for service principal authentication
-`src/auth/oauth/token.rs` — `OAuthToken` struct with `is_expired()`, `is_stale()`, JSON serialization
41
+
-`src/auth/oauth/oidc.rs` — `OidcEndpoints` struct, `discover()` function hitting `{host}/oidc/.well-known/oauth-authorization-server`
42
+
-`src/auth/oauth/cache.rs` — `TokenCache` with `load()`/`save()`, SHA-256 hashed filenames, 0o600 permissions
43
+
-`src/auth/oauth/token_store.rs` — `TokenStore` with `RwLock<Option<OAuthToken>>`, `AtomicBool` for refresh coordination, FRESH/STALE/EXPIRED state machine
-`src/client/http.rs` — change `auth_provider` from `Arc<dyn AuthProvider>` to `OnceLock<Arc<dyn AuthProvider>>`, add `set_auth_provider()`, update `execute()` to read from `OnceLock`
48
+
-`src/database.rs` — add `AuthMechanism`/`AuthFlow` enums (with `TryFrom<i64>`), new config fields (`auth_mechanism`, `auth_flow`, `auth_client_id`, `auth_client_secret`, `auth_scopes`, `auth_token_endpoint`, `auth_redirect_port`), `set_option()`/`get_option_string()` for all new keys
49
+
-`src/auth/mod.rs` — add `pub mod oauth;`, remove old `pub use oauth::OAuthCredentials`, delete `src/auth/oauth.rs` (replaced by directory module)
**Definition of done:** All foundation modules compile, unit tests pass, `DatabricksHttpClient` two-phase init works, database config parsing works for all auth options.
60
+
61
+
---
62
+
63
+
### Task 2: M2M Provider (Client Credentials)
64
+
65
+
**Scope:** Implement the M2M OAuth flow using `oauth2::BasicClient` for client credentials exchange.
**Definition of done:** M2M flow works end-to-end from `Database` config through `new_connection()` to `get_auth_header()`. Wiremock tests verify the full OIDC discovery -> token exchange -> refresh cycle.
-`src/database.rs` — wire `AuthFlow::Browser` match arm in `new_connection()` to create `AuthorizationCodeProvider`
102
+
103
+
**Implementation details:**
104
+
- On creation: try loading cached token via `TokenCache::load()`
105
+
- If cached token has valid `refresh_token`: store in `TokenStore`, refresh on first `get_auth_header()` if stale/expired
106
+
- If no cache: generate PKCE via `PkceCodeChallenge::new_random_sha256()`, build auth URL via `client.authorize_url()`, start `CallbackServer`, launch browser via `open::that()`, wait for callback, exchange code via `client.exchange_code().set_pkce_verifier()`
107
+
- Save token to cache after every successful acquisition/refresh
108
+
- Refresh uses `client.exchange_refresh_token()` through `DatabricksHttpClient::execute_without_auth()`
109
+
- If refresh token is expired, fall back to full browser flow
**Definition of done:** U2M flow works end-to-end. Token cache persists across connections. Refresh path works via wiremock tests. Browser flow verified manually via `#[ignore]` E2E test.
-`get_or_refresh(refresh_fn)`: Returns a valid token. If STALE, spawns background refresh via `std::thread::spawn` and returns current token. If EXPIRED, blocks caller until refresh completes.
183
+
-`get_or_refresh(refresh_fn)`: Returns a valid token. If STALE, spawns background refresh via `tokio::spawn` and returns current token. If EXPIRED, blocks caller until refresh completes.
184
184
- Thread-safe: `RwLock` for read-heavy access, `AtomicBool` to prevent concurrent refresh.
185
185
- Only one refresh runs at a time; concurrent callers receive the current (stale) token.
Following the Databricks ODBC driver's two-level authentication scheme, configuration uses `AuthMech` (mechanism) and `Auth_Flow` (OAuth flow type) as the primary selectors. Both are **required** -- no auto-detection.
365
+
Authentication is configured via a single `databricks.auth.type` string option that selects the authentication method. This replaces the ODBC-style two-level `AuthMech`/`Auth_Flow` numeric scheme with self-describing string values.
|`databricks.auth.token_endpoint`| String | Auto-discovered via OIDC | No | Override OIDC-discovered token endpoint |
421
403
|`databricks.auth.redirect_port`| String |`"8020"`| No | Localhost port for browser callback server |
422
404
423
-
Both `mechanism` and `flow` are mandatory -- no auto-detection. This makes configuration explicit and predictable, matching the ODBC driver's approach where `AuthMech` and `Auth_Flow` are always specified.
405
+
`databricks.auth.type` is mandatory -- no auto-detection. This makes configuration explicit and predictable.
424
406
425
407
---
426
408
@@ -447,9 +429,9 @@ The `AuthProvider::get_auth_header()` trait method is synchronous, but OAuth tok
0 commit comments