feat(rust): OAuth auth config, U2M and M2M providers#322
feat(rust): OAuth auth config, U2M and M2M providers#322vikrantpuppala merged 7 commits intoadbc-drivers:mainfrom
Conversation
104743c to
6006df1
Compare
bba99fe to
0919bed
Compare
Range-diff: stack/pr-database-config (bba99fe -> 0919bed)
Reproduce locally: |
0919bed to
c966899
Compare
## 🥞 Stacked PR Use this [link](https://github.com/adbc-drivers/databricks/pull/319/files) to review incremental changes. - [**stack/oauth-u2m-m2m-design**](#319) [[Files changed](https://github.com/adbc-drivers/databricks/pull/319/files)] - [stack/pr-oauth-foundation](#320) [[Files changed](https://github.com/adbc-drivers/databricks/pull/320/files/250ff3d91c3001f671f08084f68e949e556bc5d2..bd474c189621aa70c1f14e97c32d64605275e07d)] - [stack/pr-database-config](#321) [[Files changed](https://github.com/adbc-drivers/databricks/pull/321/files/bd474c189621aa70c1f14e97c32d64605275e07d..296931cd396d82dccb1b548a51f6b9d31be3683e)] - [stack/pr-u2m-provider](#322) [[Files changed](https://github.com/adbc-drivers/databricks/pull/322/files/296931cd396d82dccb1b548a51f6b9d31be3683e..c96689981e79c04f43e8251f2cbd5690371dfca5)] - [stack/pr-integration-tests](#323) [[Files changed](https://github.com/adbc-drivers/databricks/pull/323/files/c96689981e79c04f43e8251f2cbd5690371dfca5..83d639337ca30688abb7bdba85aa16426d76eb31)] - [stack/pr-final-validation](#324) [[Files changed](https://github.com/adbc-drivers/databricks/pull/324/files/83d639337ca30688abb7bdba85aa16426d76eb31..e2cd82bf1e9510169735774784591074f30351d3)] --------- ## Summary - Design document for adding OAuth 2.0 authentication to the Rust ADBC driver covering both U2M (Authorization Code + PKCE) and M2M (Client Credentials) flows - Sprint plan breaking the implementation into 3 tasks: foundation + HTTP client changes, M2M provider, U2M provider - Uses the `oauth2` crate for protocol-level operations, unified `DatabricksHttpClient` with two-phase `OnceLock` init, and ODBC-aligned numeric config values (`AuthMech`/`Auth_Flow`) ## Key decisions and alternatives considered - **`oauth2` crate adoption** over hand-rolling OAuth protocol (eliminates ~200 lines of boilerplate, handles PKCE/token exchange/refresh) - **Unified HTTP client** (`DatabricksHttpClient` with `OnceLock`) over separate `reqwest::Client` for token calls (shared retry logic, connection pooling) - **ODBC-aligned numeric config** (`mechanism=0/11`, `flow=0/1/2`) over string-based or auto-detection (explicit, predictable, matches ODBC driver) - **Separate U2M/M2M providers** over single OAuthProvider (different flows, refresh strategies, caching needs) - **Separate token cache** (`~/.config/databricks-adbc/oauth/`) over sharing Python SDK cache (fragile cross-SDK compatibility) ## Areas needing specific review focus - Two-phase HTTP client initialization pattern (OnceLock for auth provider) — is this the right approach for breaking the circular dependency? - Token refresh state machine (FRESH/STALE/EXPIRED) — are the thresholds (40s expiry buffer, min(TTL*0.5, 20min) stale) appropriate? - Config option naming (`databricks.auth.mechanism`, `databricks.auth.flow`) — alignment with ODBC driver - Sprint plan task breakdown — is the scope realistic for 2 weeks? --- *Replaces #318 (closed — converted to stacked branch)* 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
ec7600d to
5e41800
Compare
1374f0b to
abc00ce
Compare
…tore (#320) ## 🥞 Stacked PR Use this [link](https://github.com/adbc-drivers/databricks/pull/320/files) to review incremental changes. - [**stack/pr-oauth-foundation**](#320) [[Files changed](https://github.com/adbc-drivers/databricks/pull/320/files)] - [stack/pr-database-config](#321) [[Files changed](https://github.com/adbc-drivers/databricks/pull/321/files/78b9ec88459f895c76bd1aea99fcb47e5eb94893..164ada04d14660306c7e44dd3d52a7943050aa27)] - [stack/pr-u2m-provider](#322) [[Files changed](https://github.com/adbc-drivers/databricks/pull/322/files/164ada04d14660306c7e44dd3d52a7943050aa27..abc00ced51d89f1a652f78209f692775eba05e73)] - [stack/pr-integration-tests](#323) [[Files changed](https://github.com/adbc-drivers/databricks/pull/323/files/abc00ced51d89f1a652f78209f692775eba05e73..75b18d6c594eeba89a30450152d6d6f672239614)] - [stack/pr-final-validation](#324) [[Files changed](https://github.com/adbc-drivers/databricks/pull/324/files/75b18d6c594eeba89a30450152d6d6f672239614..2d6ccb09e121015aa6a0da6e992529a686bb0f04)] --------- ## Summary Adds the core OAuth token infrastructure used by both U2M and M2M flows: - **`OAuthToken`** — token struct with expiry tracking, stale detection (40s buffer / 50% TTL), and serde support - **OIDC discovery** — fetches `authorization_endpoint` and `token_endpoint` from `/.well-known/oauth-authorization-server` - **`TokenCache`** — file-based persistence at `~/.config/databricks-adbc/oauth/` with SHA-256 hashed filenames and `0o600` permissions - **`TokenStore`** — thread-safe token lifecycle (Empty → Fresh → Stale → Expired) with coordinated refresh via `RwLock` + `AtomicBool` - **Cargo dependencies** — `oauth2`, `sha2`, `dirs`, `serde`, `open` crates - **`DatabricksHttpClient`** — extended with `OnceLock`-based auth provider and `inner()` accessor for the `oauth2` crate ### Key files - `src/auth/oauth/token.rs` — `OAuthToken` struct - `src/auth/oauth/oidc.rs` — OIDC endpoint discovery - `src/auth/oauth/cache.rs` — file-based token cache - `src/auth/oauth/token_store.rs` — token lifecycle state machine - `src/client/http.rs` — HTTP client auth provider integration
…: task-2.2-database-config-fields
abc00ce to
42daa83
Compare
…2.3-database-validation
…1-callback-server
…sk-3.2-u2m-provider
42daa83 to
af7f3c8
Compare
af7f3c8 to
3730127
Compare
| if let Some(v) = Self::parse_int_option(&value) { | ||
| if !(0..=65535).contains(&v) { | ||
| return Err(DatabricksErrorHelper::invalid_argument() | ||
| .message(format!( |
There was a problem hiding this comment.
should we also log this? Or is logging implicit in the error handling?
There was a problem hiding this comment.
Errors propagate to the caller via Result — the ADBC caller handles them. Adding logging here would duplicate the error path. We could add tracing::debug! for successful option sets, but that's noisy for normal operation. I'd leave it as-is.
| .expect("access_token should be validated above") | ||
| .clone(), | ||
| )), | ||
| AuthType::OAuthM2m => { |
There was a problem hiding this comment.
but you are adding these providers, will this stitching be done in separate PR?
There was a problem hiding this comment.
Yes — M2M is fully wired up in PR #323 (the next PR in the stack). That PR adds ClientCredentialsProvider to the AuthType::OAuthM2m match arm in new_connection(), along with integration tests for both M2M and U2M.
| /// Receiver for callback results. | ||
| result_rx: Option<oneshot::Receiver<CallbackResult>>, | ||
| /// Handle to the server task. | ||
| server_handle: Option<tokio::task::JoinHandle<()>>, |
There was a problem hiding this comment.
shall we also implement Drop, which can abort the JoinHandle, if wait_for_code is not called due to some error?
There was a problem hiding this comment.
Good catch — added impl Drop for CallbackServer that aborts the server_handle if it's still running. Handles the case where an error occurs before wait_for_code is called.
…3-database-integration
3730127 to
b67c125
Compare
🥞 Stacked PR
Use this link to review incremental changes.
Summary
Adds the
AuthTypeconfiguration model and both OAuth authentication providers, wired intoDatabase::new_connection():Auth config (
config.rs,database.rs)AuthTypeenum —AccessToken,OAuthM2m,OAuthU2mparsed fromdatabricks.auth.typestring optionAuthConfig— collects all auth options (auth_type,client_id,client_secret,scopes,token_endpoint,redirect_port) with validationDatabase::set_option/new_connection()— flatmatch auth_typedispatches to the appropriate providerOAuth providers
CallbackServer(callback.rs) — lightweight TCP server on localhost for OAuth redirect callbacks with CSRF state validationAuthorizationCodeProvider(U2M) (u2m.rs) — authorization code + PKCE flow: OIDC discovery, browser launch, callback capture, token exchange, refresh with disk cachingClientCredentialsProvider(M2M) (m2m.rs) — client credentials grant withTokenStore-managed lifecycleConfiguration
Key files
src/auth/config.rs—AuthTypeenum andAuthConfigvalidationsrc/auth/oauth/callback.rs— OAuth redirect callback serversrc/auth/oauth/u2m.rs— U2M providersrc/auth/oauth/m2m.rs— M2M providersrc/database.rs— option parsing and auth provider creationThis pull request was AI-assisted by Isaac.