Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
70089dc
Add a non-interactive device code login
maksim-zuev-db Oct 29, 2026
f0b4dbe
Share the device code approval instructions between both logins
maksim-zuev-db Oct 29, 2026
2326100
Test that completing a device code login checks the user code
maksim-zuev-db Oct 29, 2026
af33dc6
Delete the pending device code login after a successful login
maksim-zuev-db Oct 29, 2026
d60c40b
Reject an empty --user-code or --confirmation-code
maksim-zuev-db Oct 29, 2026
9bb2edb
Remove device code login tests that the retry test covers
maksim-zuev-db Oct 29, 2026
8b5aec3
Pass the context to NewDeviceCodeLogin in the non-interactive login
maksim-zuev-db Oct 29, 2026
a87ed03
Use require and assert in TestMemoryStore_PendingLoginUsesCopies
maksim-zuev-db Oct 29, 2026
8a9d64e
Keep pending device code logins apart from the tokens
maksim-zuev-db Oct 29, 2026
77faeda
Complete a device code login only with the settings that started it
maksim-zuev-db Oct 29, 2026
e3c67b7
Skip the workspace picker in the non-interactive device code login
maksim-zuev-db Oct 29, 2026
43207fa
Revert "Keep pending device code logins apart from the tokens"
maksim-zuev-db Oct 29, 2026
05dd7de
Revert "Skip the workspace picker in the non-interactive device code …
maksim-zuev-db Oct 29, 2026
959b693
Treat a token store entry without a token as a missing token
maksim-zuev-db Oct 30, 2026
41a45d3
Reject profile names that end with #device-code in auth login
maksim-zuev-db Oct 30, 2026
1508d4a
Start a new device code login after the token requests are rate limited
maksim-zuev-db Oct 30, 2026
c7b2135
Compare the canonical host when completing a device code login
maksim-zuev-db Oct 30, 2026
64d5acc
Tell that --user-code does not match instead of blaming a newer login
maksim-zuev-db Oct 30, 2026
49bc70d
Say which prompts the non-interactive device code login skips
maksim-zuev-db Oct 30, 2026
2931613
Do not rewrite the token cache file when deleting a missing key
maksim-zuev-db Oct 30, 2026
8856bdb
Delete the pending device code login of any profile in auth logout
maksim-zuev-db Oct 30, 2026
b69120e
Copy the scopes of a pending login in the memory store
maksim-zuev-db Oct 30, 2026
33e396a
Use cmdio.MockDiscard in the device code login completion tests
maksim-zuev-db Oct 30, 2026
7f4e35a
Ignore a token store error in the logout of a non-login profile
maksim-zuev-db Nov 2, 2026
03ff5f9
Keep the pending device code login after a rate limited token request
maksim-zuev-db Nov 2, 2026
7d8de6f
Compare the scopes of a pending device code login in any order
maksim-zuev-db Nov 2, 2026
dd9cf58
Delete the pending device code login only when it completes
maksim-zuev-db Nov 2, 2026
a12cb57
Issue a new user code for every device code login in the fake server
maksim-zuev-db Nov 2, 2026
275c55c
Simplify the checks of the non-interactive device code login
maksim-zuev-db Nov 2, 2026
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
20 changes: 0 additions & 20 deletions acceptance/cmd/auth/login/device-code/nominal/script

This file was deleted.

1 change: 0 additions & 1 deletion acceptance/cmd/auth/login/device-code/nominal/test.toml

This file was deleted.

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

=== A second login for the same profile replaces the first

>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code

>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code

>>> cache_keys
[
"test#device-code"
]

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
Error: --user-code does not match the pending login for profile test. Check the user code. If you started a newer login, complete it with its user code. Run the same command without --user-code and --confirmation-code to start a new login

>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-0002 --confirmation-code FTFA-QQKY
Profile test was successfully saved

>>> print_requests.py --oneline //oidc/v1/token
{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-2&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}

=== Logins for different targets are independent

>>> [CLI] auth login --host [DATABRICKS_URL] --profile a --use-device-code

>>> [CLI] auth login --host [DATABRICKS_URL] --profile b --use-device-code

>>> [CLI] auth login --host [DATABRICKS_URL] --use-device-code

>>> cache_keys
[
"a#device-code",
"b#device-code",
"[DATABRICKS_URL]",
"[DATABRICKS_URL]#device-code",
"test"
]

>>> [CLI] auth login --host [DATABRICKS_URL] --profile b --use-device-code --user-code WDJB-0004 --confirmation-code FTFA-QQKY
Profile b was successfully saved

>>> [CLI] auth login --host [DATABRICKS_URL] --use-device-code --user-code WDJB-0005 --confirmation-code FTFA-QQKY

>>> [CLI] auth login --host [DATABRICKS_URL] --profile a --use-device-code --user-code WDJB-0003 --confirmation-code FTFA-QQKY
Profile a was successfully saved

>>> cache_keys
[
"a",
"b",
"[DATABRICKS_URL]",
"test"
]

>>> print_requests.py --oneline //oidc/v1/token
{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-4&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-5&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-3&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
sethome "./home"

# The fake server issues the user codes WDJB0001, WDJB0002, ... in order.
title "A second login for the same profile replaces the first\n"
start_login LOG.first $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
start_login LOG.second $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
trace cache_keys
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-0002 --confirmation-code FTFA-QQKY
trace print_requests.py --oneline //oidc/v1/token

title "Logins for different targets are independent\n"
start_login LOG.a $CLI auth login --host $DATABRICKS_HOST --profile a --use-device-code
start_login LOG.b $CLI auth login --host $DATABRICKS_HOST --profile b --use-device-code
start_login LOG.host $CLI auth login --host $DATABRICKS_HOST --use-device-code
trace cache_keys
trace $CLI auth login --host $DATABRICKS_HOST --profile b --use-device-code --user-code WDJB-0004 --confirmation-code FTFA-QQKY
trace $CLI auth login --host $DATABRICKS_HOST --use-device-code --user-code WDJB-0005 --confirmation-code FTFA-QQKY
trace $CLI auth login --host $DATABRICKS_HOST --profile a --use-device-code --user-code WDJB-0003 --confirmation-code FTFA-QQKY
trace cache_keys
trace print_requests.py --oneline //oidc/v1/token
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[resources]
host = $DATABRICKS_HOST
resources = https://resource.test
auth_type = databricks-cli
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@

=== Device code login is disabled by default

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
Error: --use-device-code is experimental. Set DATABRICKS_CLI_DEVICE_CODE=true to enable it

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
Error: --use-device-code is experimental. Set DATABRICKS_CLI_DEVICE_CODE=true to enable it

=== Flags the non-interactive login cannot honor

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --configure-cluster
Error: --configure-cluster is not supported by the non-interactive device code login. Set cluster_id on the profile after the login

=== Missing host

>>> musterr [CLI] auth login --use-device-code
Error: --use-device-code requires a workspace host. Pass it as an argument or with --host

>>> musterr [CLI] auth login --profile test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
Error: --use-device-code requires a workspace host. Pass it as an argument or with --host

=== Incomplete flags of the second step

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --confirmation-code FTFA-QQKY
Error: --confirmation-code requires --user-code

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-0001
Error: --user-code requires --confirmation-code

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --user-code WDJB-0001 --confirmation-code FTFA-QQKY
Error: --confirmation-code requires --use-device-code

=== Profile name that marks a pending login

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test#device-code --use-device-code
Error: profile name "test#device-code" must not end with #device-code, which marks pending device code logins

=== Account host

>>> musterr [CLI] auth login --host https://accounts.cloud.databricks.test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
Warn: [hostmetadata] failed to fetch host metadata for https://accounts.cloud.databricks.test, will skip for 1m0s
Error: --use-device-code is only supported for workspace hosts

=== Profile scoped to resources

>>> musterr [CLI] auth login --profile resources --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
Error: --use-device-code cannot be used with profile resources because its login is scoped to resources. Log in without --use-device-code

=== No pending login

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
Error: no pending device code login for profile test. Run the same command without --user-code and --confirmation-code to start a new login

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
Error: no pending device code login for host [DATABRICKS_URL]. Run the same command without --user-code and --confirmation-code to start a new login

>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code

=== Wrong confirmation code

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-0001 --confirmation-code AAAA-AAAA
Error: token exchange: Invalid confirmation code (error code: invalid_request). Check the confirmation code and approve the login if you haven't, then rerun this command. Run the same command without --user-code and --confirmation-code to start a new login if it still fails

=== Rerun with the right confirmation code

>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code wdjb-0001 --confirmation-code ftfa-qqky
Profile test was successfully saved

=== Rerun after the login was completed

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
Error: no pending device code login for profile test. Run the same command without --user-code and --confirmation-code to start a new login

=== Device code unknown to the server

>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code

>>> cp home/.databricks/token-cache.json home/token-cache.pending.json

>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-0002 --confirmation-code FTFA-QQKY
Profile test was successfully saved

>>> cp home/token-cache.pending.json home/.databricks/token-cache.json

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-0002 --confirmation-code FTFA-QQKY
Error: token exchange: Invalid device code (error code: invalid_request). Run the same command without --user-code and --confirmation-code to start a new login

>>> cache_keys
[
"[DATABRICKS_URL]",
"test"
]

=== Scopes changed between the steps

>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --scopes sql

>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-0003 --confirmation-code FTFA-QQKY
Error: the login for profile test was started with different scopes. Complete it with the same command that started it. Run the same command without --user-code and --confirmation-code to start a new login

>>> print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token
{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+all-apis"}
{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=AAAAAAAA&device_code=device-code-1&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-1&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+all-apis"}
{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-2&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-2&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+sql"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
sethome "./home"

title "Device code login is disabled by default\n"
(
unset DATABRICKS_CLI_DEVICE_CODE
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
)

title "Flags the non-interactive login cannot honor\n"
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --configure-cluster

# The non-interactive login cannot answer the profile picker or the host prompt.
title "Missing host\n"
trace musterr $CLI auth login --use-device-code
trace musterr $CLI auth login --profile test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY

title "Incomplete flags of the second step\n"
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --confirmation-code FTFA-QQKY
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-0001
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --user-code WDJB-0001 --confirmation-code FTFA-QQKY

title "Profile name that marks a pending login\n"
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile 'test#device-code' --use-device-code

title "Account host\n"
trace musterr $CLI auth login --host https://accounts.cloud.databricks.test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY

envsubst < "./home/.databrickscfg.tmpl" > "./home/.databrickscfg"

title "Profile scoped to resources\n"
trace musterr $CLI auth login --profile resources --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY

title "No pending login\n"
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
trace musterr $CLI auth login --host $DATABRICKS_HOST --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY

start_login LOG.wrong-code $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code

title "Wrong confirmation code\n"
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-0001 --confirmation-code AAAA-AAAA

# A wrong confirmation code leaves the device code valid, so the pending
# login is kept.
title "Rerun with the right confirmation code\n"
trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code wdjb-0001 --confirmation-code ftfa-qqky

title "Rerun after the login was completed\n"
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY

# The server discards the device code when the login expires. The fake server
# only discards a device code once it is exchanged, so a pending login restored
# from before the exchange stands in for an expired one. The pending login is
# then deleted.
title "Device code unknown to the server\n"
start_login LOG.expired $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
trace cp home/.databricks/token-cache.json home/token-cache.pending.json
trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-0002 --confirmation-code FTFA-QQKY
trace cp home/token-cache.pending.json home/.databricks/token-cache.json
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-0002 --confirmation-code FTFA-QQKY
trace cache_keys

# The token gets the scopes of the device authorization, so the second step
# cannot change them.
title "Scopes changed between the steps\n"
start_login LOG.scopes $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --scopes sql
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-0003 --confirmation-code FTFA-QQKY

trace print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[scoped]
host = $DATABRICKS_HOST
scopes = sql
client_id = databricks-cli
auth_type = databricks-cli
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
; The profile defined in the DEFAULT section is to be used as a fallback when no profile is explicitly specified.
[DEFAULT]

[scoped]
host = [DATABRICKS_URL]
scopes = sql
auth_type = databricks-cli
workspace_id = [WORKSPACE_ID]
client_id = databricks-cli

[serverless]
host = [DATABRICKS_URL]
serverless_compute_id = auto
workspace_id = [WORKSPACE_ID]
auth_type = databricks-cli
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Cloud = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

=== Scopes and client ID from the profile

>>> [CLI] auth login --profile scoped --use-device-code
Using a browser on another device, visit:
[DATABRICKS_URL]/device/verification

And enter the code:
WDJB-0001

Once finished, complete the login by running the same command with
--user-code WDJB-0001 and --confirmation-code set to the confirmation code
provided in your browser.

>>> [CLI] auth login --profile scoped --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
Profile scoped was successfully saved

>>> print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token
{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+sql"}
{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=device-code-1&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}

=== Serverless compute is configured in the second step

>>> [CLI] auth login --host [DATABRICKS_URL] --profile serverless --use-device-code --configure-serverless
Using a browser on another device, visit:
[DATABRICKS_URL]/device/verification

And enter the code:
WDJB-0002

Once finished, complete the login by running the same command with
--user-code WDJB-0002 and --confirmation-code set to the confirmation code
provided in your browser.

>>> [CLI] auth login --host [DATABRICKS_URL] --profile serverless --use-device-code --configure-serverless --user-code WDJB-0002 --confirmation-code FTFA-QQKY
Profile serverless was successfully saved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
sethome "./home"
envsubst < "./home/.databrickscfg.tmpl" > "./home/.databrickscfg"

# The rerun resolves the login from the same flags and profile as the first
# step, so the token exchange uses the client ID of the profile.
title "Scopes and client ID from the profile\n"
trace $CLI auth login --profile scoped --use-device-code
trace $CLI auth login --profile scoped --use-device-code --user-code WDJB-0001 --confirmation-code FTFA-QQKY
trace print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token

# Only the second step saves the profile.
title "Serverless compute is configured in the second step\n"
trace $CLI auth login --host $DATABRICKS_HOST --profile serverless --use-device-code --configure-serverless
trace $CLI auth login --host $DATABRICKS_HOST --profile serverless --use-device-code --configure-serverless --user-code WDJB-0002 --confirmation-code FTFA-QQKY

mv "./home/.databrickscfg" "./out.databrickscfg"
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,3 @@ auth_type = databricks-cli

[__settings__]
default_profile = test

[scoped]
host = [DATABRICKS_URL]
workspace_id = [WORKSPACE_ID]
scopes = sql
auth_type = databricks-cli
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Cloud = false
Loading
Loading