Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

=== Profiles before logout — logfood should be valid

>>> [CLI] auth profiles
Name Host Valid
logfood (Default) [DATABRICKS_URL] YES

=== Token cache keys before logout
[
"[DATABRICKS_URL]",
"logfood"
]

=== Logout without --delete
>>> [CLI] auth logout --profile logfood --force
Logged out of profile "logfood". Use --delete to also remove it from the config file.

=== Config after logout — profile should still exist
; The profile defined in the DEFAULT section is to be used as a fallback when no profile is explicitly specified.
[DEFAULT]

[logfood]
host = [DATABRICKS_URL]
account_id = stale-account
auth_type = databricks-cli

[__settings__]
default_profile = logfood

=== Token cache keys after logout — both entries should be removed
[]

=== Profiles after logout — logfood should be invalid

>>> [CLI] auth profiles
Name Host Valid
logfood (Default) [DATABRICKS_URL] NO

=== Logged out profile should no longer return a token

>>> musterr [CLI] auth token --profile logfood
Error: cache: databricks OAuth is not configured for this host. Try logging in again with `databricks auth login --profile logfood` before retrying. If this fails, please report this issue to the Databricks CLI maintainers at https://github.com/databricks/cli/issues/new
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
sethome "./home"

cat > "./home/.databrickscfg" <<EOF
; The profile defined in the DEFAULT section is to be used as a fallback when no profile is explicitly specified.
[DEFAULT]

[logfood]
host = ${DATABRICKS_HOST}
account_id = stale-account
auth_type = databricks-cli

[__settings__]
default_profile = logfood
EOF

mkdir -p "./home/.databricks"
cat > "./home/.databricks/token-cache.json" <<EOF
{
"version": 1,
"tokens": {
"logfood": {
"access_token": "logfood-cached-token",
"token_type": "Bearer"
},
"${DATABRICKS_HOST}": {
"access_token": "logfood-host-token",
"token_type": "Bearer"
}
}
}
EOF

title "Profiles before logout — logfood should be valid\n"
trace $CLI auth profiles

title "Token cache keys before logout\n"
jq -S '.tokens | keys' "./home/.databricks/token-cache.json"

title "Logout without --delete"
trace $CLI auth logout --profile logfood --force

title "Config after logout — profile should still exist\n"
cat "./home/.databrickscfg"

title "Token cache keys after logout — both entries should be removed\n"
jq -S '.tokens | keys' "./home/.databricks/token-cache.json"

title "Profiles after logout — logfood should be invalid\n"
trace $CLI auth profiles

title "Logged out profile should no longer return a token\n"
trace musterr $CLI auth token --profile logfood
21 changes: 16 additions & 5 deletions cmd/auth/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,18 +276,29 @@ func clearTokenCache(ctx context.Context, p profile.Profile, profiler profile.Pr
// hostCacheKeyAndMatchFn returns the token cache key and a profile match
// function for the host-based token entry. Account and unified profiles use
// host/oidc/accounts/<account_id> as the cache key and match on both host and
// account ID; workspace profiles use just the host.
// account ID. Workspace profiles use just the host, even if the profile still
// carries stale account metadata from an earlier login.
func hostCacheKeyAndMatchFn(p profile.Profile) (string, profile.ProfileMatchFunction) {
host := (&config.Config{Host: p.Host}).CanonicalHostName()
cfg := &config.Config{
Host: p.Host,
AccountID: p.AccountID,
WorkspaceID: p.WorkspaceID,
Experimental_IsUnifiedHost: p.IsUnifiedHost,
}
host := cfg.CanonicalHostName()
if host == "" {
return "", nil
}

if p.AccountID != "" {
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)
}
Comment on lines +293 to 301
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.


return host, profile.WithHost(host)
}

// resolveLogoutArg resolves a positional argument to a profile name. It first
Expand Down
24 changes: 19 additions & 5 deletions cmd/auth/logout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ auth_type = databricks-cli
host = https://my-unique-workspace.cloud.databricks.com
auth_type = databricks-cli

[my-workspace-stale-account]
host = https://stale-account.cloud.databricks.com
account_id = stale-account
auth_type = databricks-cli

[my-account]
host = https://accounts.cloud.databricks.com
account_id = abc123
Expand All @@ -43,13 +48,15 @@ token = dev-token
`

var logoutTestTokensCacheConfig = map[string]*oauth2.Token{
"my-workspace": {AccessToken: "shared-workspace-token"},
"shared-workspace": {AccessToken: "shared-workspace-token"},
"my-unique-workspace": {AccessToken: "my-unique-workspace-token"},
"my-account": {AccessToken: "my-account-token"},
"my-unified": {AccessToken: "my-unified-token"},
"my-workspace": {AccessToken: "shared-workspace-token"},
"shared-workspace": {AccessToken: "shared-workspace-token"},
"my-unique-workspace": {AccessToken: "my-unique-workspace-token"},
"my-workspace-stale-account": {AccessToken: "stale-account-token"},
"my-account": {AccessToken: "my-account-token"},
"my-unified": {AccessToken: "my-unified-token"},
"https://my-workspace.cloud.databricks.com": {AccessToken: "shared-workspace-host-token"},
"https://my-unique-workspace.cloud.databricks.com": {AccessToken: "unique-workspace-host-token"},
"https://stale-account.cloud.databricks.com": {AccessToken: "stale-account-host-token"},
"https://accounts.cloud.databricks.com/oidc/accounts/abc123": {AccessToken: "account-host-token"},
"https://unified.cloud.databricks.com/oidc/accounts/def456": {AccessToken: "unified-host-token"},
"my-m2m": {AccessToken: "m2m-service-token"},
Expand Down Expand Up @@ -96,6 +103,13 @@ func TestLogout(t *testing.T) {
isSharedKey: false,
force: true,
},
{
name: "existing workspace profile with stale account id",
profileName: "my-workspace-stale-account",
hostBasedKey: "https://stale-account.cloud.databricks.com",
isSharedKey: false,
force: true,
},
{
name: "existing account profile",
profileName: "my-account",
Expand Down
Loading