Skip to content

Fix auth logout failing to clear token for workspace profiles with account ID#4853

Merged
mihaimitrea-db merged 3 commits intomainfrom
mihaimitrea-db/logout-workspace-accountid-bug
Apr 10, 2026
Merged

Fix auth logout failing to clear token for workspace profiles with account ID#4853
mihaimitrea-db merged 3 commits intomainfrom
mihaimitrea-db/logout-workspace-accountid-bug

Conversation

@mihaimitrea-db
Copy link
Copy Markdown
Contributor

@mihaimitrea-db mihaimitrea-db commented Mar 26, 2026

Summary

Fix auth logout failing to clear cached tokens for workspace profiles that carry an account_id in ~/.databrickscfg.

Background

The CLI's token cache stores tokens under two keys: a profile key (e.g. "logfood") and a host key (either host for workspace profiles or host/oidc/accounts/<id> for account/SPOG profiles). The host key shape is determined at login time by ToOAuthArgument, which calls .well-known/databricks-config to discover whether the host uses workspace-scoped or account-scoped OIDC.

The old hostCacheKeyAndMatchFn in auth logout used p.AccountID != "" as a proxy for "use the account-style cache key." This breaks because:

  • Since PR Discovery-driven login with workspace selection for SPOG hosts #4809, runHostDiscovery populates account_id on every workspace profile from the .well-known endpoint. A regular workspace profile now routinely has account_id set.
  • The profile fields (account_id, workspace_id, experimental_is_unified_host) cannot reliably distinguish between a classic workspace, a SPOG host, or stale metadata from a prior login. The only reliable discriminator is the oidc_endpoint shape from .well-known, which is resolved at runtime and not persisted to the profile.

Changes

hostCacheKeyAndMatchFn now delegates to auth.AuthArguments.ToOAuthArgument().GetCacheKey() — the same routing logic the SDK uses when writing the token during login. This includes the .well-known/databricks-config network call that correctly distinguishes workspace hosts from SPOG hosts.

Four acceptance tests that used hardcoded fake hostnames were switched to $DATABRICKS_HOST (the mock test server) so that .well-known resolves without warnings.

Tests

  • Unit: TestHostCacheKeyAndMatchFn — table-driven test with mock HTTP servers covering classic workspace, stale-account workspace, classic account, unified, and SPOG profiles.
  • Unit: TestLogoutSPOGProfile — verifies both profile-keyed and host-keyed tokens are cleared for a SPOG profile.
  • Unit: TestLogout table — includes a stale-account workspace row.
  • Acceptance: stale-account-id-workspace-host — end-to-end test: workspace profile with stale account_id, runs logout, verifies token cache is cleared, auth profiles shows invalid, auth token fails.
  • go test ./cmd/auth and go test ./acceptance -run TestAccept/cmd/auth/logout pass.

@mihaimitrea-db mihaimitrea-db changed the title Fix auth logout failing to clear token for workspace profiles with st… Fix auth logout failing to clear token for workspace profiles with account ID Mar 26, 2026
@eng-dev-ecosystem-bot
Copy link
Copy Markdown
Collaborator

eng-dev-ecosystem-bot commented Mar 26, 2026

Commit: 4a4b68e

Run: 23604929804

Env 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 7 10 270 805 7:06
💚​ aws windows 7 10 272 803 5:29
💚​ aws-ucws linux 7 10 366 721 19:09
💚​ aws-ucws windows 7 10 368 719 17:12
💚​ azure linux 1 12 273 803 7:03
💚​ azure windows 1 12 275 801 4:50
💚​ azure-ucws linux 1 12 371 717 17:57
💚​ azure-ucws windows 1 12 373 715 15:26
💚​ gcp linux 1 12 269 806 6:41
💚​ gcp windows 1 12 271 804 6:42
17 interesting tests: 10 SKIP, 7 RECOVERED
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/resources/permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/resources/postgres_branches/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/update_protected 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/without_branch_id 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_projects/update_display_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/synced_database_tables/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
Top 46 slowest tests (at least 2 minutes):
duration env testname
5:48 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
5:30 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
5:21 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
5:02 aws-ucws linux TestAccept/bundle/deploy/files/no-snapshot-sync/DATABRICKS_BUNDLE_ENGINE=terraform
4:47 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
4:31 aws-ucws windows TestAccept/bundle/resources/permissions/jobs/delete_one/cloud/DATABRICKS_BUNDLE_ENGINE=direct
3:52 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:51 aws-ucws windows TestAccept/bundle/resources/model_serving_endpoints/basic/DATABRICKS_BUNDLE_ENGINE=terraform
3:49 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:41 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:36 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:31 azure-ucws windows TestAccept/bundle/destroy/jobs-and-pipeline/DATABRICKS_BUNDLE_ENGINE=direct
3:25 aws-ucws windows TestAccept/bundle/resources/volumes/recreate/DATABRICKS_BUNDLE_ENGINE=direct
3:24 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:21 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:16 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:15 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:11 azure-ucws linux TestAccept/bundle/deploy/files/no-snapshot-sync/DATABRICKS_BUNDLE_ENGINE=terraform
3:11 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:06 aws-ucws linux TestAccept/bundle/deployment/bind/job/generate-and-bind/DATABRICKS_BUNDLE_ENGINE=direct
2:57 azure-ucws linux TestAccept/bundle/resources/permissions/jobs/delete_one/cloud/DATABRICKS_BUNDLE_ENGINE=direct
2:54 aws-ucws linux TestAccept/bundle/resources/permissions/jobs/delete_one/cloud/DATABRICKS_BUNDLE_ENGINE=terraform
2:50 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:48 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:48 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:47 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:46 aws-ucws linux TestAccept/bundle/resources/volumes/recreate/DATABRICKS_BUNDLE_ENGINE=direct
2:43 aws-ucws linux TestAccept/bundle/generate/auto-bind/DATABRICKS_BUNDLE_ENGINE=terraform
2:42 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:39 azure-ucws linux TestAccept/bundle/resources/permissions/jobs/delete_one/cloud/DATABRICKS_BUNDLE_ENGINE=terraform
2:38 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:35 azure-ucws windows TestAccept/bundle/deploy/files/no-snapshot-sync/DATABRICKS_BUNDLE_ENGINE=terraform
2:33 azure-ucws windows TestAccept/bundle/resources/permissions/jobs/delete_one/cloud/DATABRICKS_BUNDLE_ENGINE=terraform
2:29 aws-ucws windows TestFilerWorkspaceFilesExtensionsReadDir
2:29 aws-ucws linux TestFilerWorkspaceFilesExtensionsReadDir
2:29 aws-ucws windows TestFilerWorkspaceFilesExtensionsStat
2:28 aws-ucws windows TestAccept/bundle/resources/jobs/check-metadata/DATABRICKS_BUNDLE_ENGINE=direct
2:28 aws-ucws windows TestAccept/bundle/deploy/files/no-snapshot-sync/DATABRICKS_BUNDLE_ENGINE=terraform
2:20 azure-ucws windows TestAccept/bundle/resources/model_serving_endpoints/basic/DATABRICKS_BUNDLE_ENGINE=direct
2:17 aws-ucws linux TestAccept/bundle/resources/model_serving_endpoints/basic/DATABRICKS_BUNDLE_ENGINE=terraform
2:15 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:11 aws-ucws windows TestAccept/bundle/resources/volumes/recreate/DATABRICKS_BUNDLE_ENGINE=terraform
2:09 azure-ucws windows TestAccept/bundle/resources/model_serving_endpoints/basic/DATABRICKS_BUNDLE_ENGINE=terraform
2:07 aws-ucws linux TestFilerReadWrite/workspace_files_extensions
2:02 azure-ucws windows TestAccept/bundle/resources/permissions/jobs/delete_one/cloud/DATABRICKS_BUNDLE_ENGINE=direct
2:01 azure-ucws linux TestSparkJarTaskDeployAndRunOnVolumes/Databricks_Runtime_15.4_LTS

@mihaimitrea-db mihaimitrea-db changed the title Fix auth logout failing to clear token for workspace profiles with account ID Fix auth logout failing to clear token for workspace profiles with account id Mar 30, 2026
@mihaimitrea-db mihaimitrea-db self-assigned this Mar 30, 2026
@mihaimitrea-db mihaimitrea-db changed the title Fix auth logout failing to clear token for workspace profiles with account id Fix auth logout failing to clear token for workspace profiles with account ID Mar 30, 2026
@mihaimitrea-db mihaimitrea-db marked this pull request as ready for review March 30, 2026 12:06
Copy link
Copy Markdown
Member

@simonfaltum simonfaltum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review from Isaac + Cursor (2-round deep review)

1 Critical | 1 Gap (Nit) | 1 Suggestion

The fix for the stale account_id case on classic workspace profiles looks correct. The concern is that switching to HostType() might regress SPOG/discovery-routed profiles. See inline comment for details.


[Gap - Nit] Missing test for the return "", nil branch
cmd/auth/logout_test.go: When HostType() returns AccountHost/UnifiedHost but p.AccountID is empty, the new code returns "", nil. This path doesn't have test coverage. It's an unlikely scenario (an accounts.* host with no account_id), but it's new behavior that differs from the old code (which would've fallen through to the workspace branch).

[Suggestion] Direct unit tests for hostCacheKeyAndMatchFn
This is a pure function with clear inputs/outputs, but it's only tested indirectly through runLogout. A table-driven TestHostCacheKeyAndMatchFn covering workspace, stale-account workspace, account, unified, and SPOG profiles would serve as documentation of the intended cache key shapes.

Comment on lines 293 to 301
switch cfg.HostType() {
case config.AccountHost, config.UnifiedHost:
if p.AccountID == "" {
return "", nil
}
return host + "/oidc/accounts/" + p.AccountID, profile.WithHostAndAccountID(host, p.AccountID)
default:
return host, profile.WithHost(host)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] HostType() might regress logout for SPOG/discovery-routed profiles

HostType() classifies hosts by URL pattern: only accounts.* becomes AccountHost, only profiles with experimental_is_unified_host become UnifiedHost. Everything else is WorkspaceHost.

The problem is SPOG/discovery-routed profiles. During login, libs/auth/arguments.go ToOAuthArgument() routes SPOG hosts (e.g. https://spog.example.com) to unified OAuth when discovery returns an account-scoped OIDC endpoint. The token gets stored under host/oidc/accounts/<account_id>. But runHostDiscovery() does NOT set Experimental_IsUnifiedHost, and discoveryLogin() explicitly clears it when saving the profile.

So a valid SPOG profile has host + account_id + workspace_id but no unified flag. With this change, HostType() returns WorkspaceHost for these profiles, logout tries to delete just <host> instead of <host>/oidc/accounts/<account_id>, and the token is left behind. The old p.AccountID != "" check actually handled this correctly (for the wrong reasons).

I think the fix needs to account for profiles that carry workspace_id (indicating they went through discovery). Those should keep the account-style cache key. Only profiles with a stale account_id but no workspace_id should get downgraded to the plain host key. Adding regression tests for a SPOG profile (host + account_id + workspace_id, no unified flag) would be good.

…ale account_id

logout derived the token-cache key by checking whether `account_id` was
set on the profile, but a workspace profile can carry a stale
`account_id` from an earlier account-level login. This caused logout to
build an account-style cache key (`host/oidc/accounts/<id>`) that did
not match the workspace-style key the SDK actually wrote, so the cached
token was never deleted.

Use `config.HostType()` instead to decide the cache-key shape, which
matches the SDK's own OAuth routing logic and correctly treats workspace
hosts as workspace profiles regardless of leftover account metadata.

Co-authored-by: Isaac
@mihaimitrea-db mihaimitrea-db force-pushed the mihaimitrea-db/logout-workspace-accountid-bug branch from 00421c4 to bf6c3da Compare April 10, 2026 08:09
The title calls used trailing \n which produced unnecessary blank lines
in the expected output. Align with the style of other titles in the
same script.
@simonfaltum
Copy link
Copy Markdown
Member

This looks good to me. We're relying on .well-known/databricks-config here but it's on a non-critical path (logout), so that's fine.

Worth noting that we might remove the dual-write for host keys in the future as part of a broader effort to move tokens to secure storage. That's currently in the planning phase and nothing is decided yet, so no need to account for it here.

Copy link
Copy Markdown
Member

@simonfaltum simonfaltum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, tests pass locally.

@mihaimitrea-db mihaimitrea-db added this pull request to the merge queue Apr 10, 2026
Merged via the queue into main with commit e8391f4 Apr 10, 2026
20 checks passed
@mihaimitrea-db mihaimitrea-db deleted the mihaimitrea-db/logout-workspace-accountid-bug branch April 10, 2026 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants