Skip to content

Commit 64abb91

Browse files
maksim-zuev-dbIsaac
andcommitted
Add a non-interactive device code login
When stdin is not a terminal, e.g. when an AI agent runs it, auth login --use-device-code stores the device code as a pending login in the token store and prints the URL and the user code. Rerunning the command with the hidden --user-code and --confirmation-code flags completes the login. The device code stays on the machine, so a confirmation code in an agent's transcript cannot complete the login elsewhere. The pending login is a new PendingLogin field of storage.Entry, and auth logout deletes it together with the token. The flow is picked with the new cmdio.IsInputTTY. The interactive login tests piped the code into stdin, which now starts the non-interactive login, so they move from acceptance tests to the terminal emulator. Co-authored-by: Isaac <no-reply@databricks.com>
1 parent 0ab827c commit 64abb91

44 files changed

Lines changed: 1049 additions & 128 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎acceptance/cmd/auth/login/device-code/nominal/script‎

Lines changed: 0 additions & 20 deletions
This file was deleted.

‎acceptance/cmd/auth/login/device-code/nominal/test.toml‎

Lines changed: 0 additions & 1 deletion
This file was deleted.

acceptance/cmd/auth/login/device-code/nominal/out.test.toml renamed to acceptance/cmd/auth/login/device-code/non-interactive/concurrent/out.test.toml

File renamed without changes.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
2+
=== A second login for the same profile replaces the first
3+
4+
>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
5+
6+
>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
7+
8+
>>> update_file.py home/.databricks/token-cache.json WDJBMJHT NEWRCODE
9+
10+
>>> cache_keys
11+
[
12+
"test#device-code"
13+
]
14+
15+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
16+
Error: a newer login for profile test replaced this one. Complete the newer login with its user code. Run the same command without --user-code and --confirmation-code to start a new login
17+
18+
>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code NEWR-CODE --confirmation-code FTFA-QQKY
19+
Profile test was successfully saved
20+
21+
>>> print_requests.py --oneline //oidc/v1/token
22+
{"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"}
23+
24+
=== Logins for different targets are independent
25+
26+
>>> [CLI] auth login --host [DATABRICKS_URL] --profile a --use-device-code
27+
28+
>>> [CLI] auth login --host [DATABRICKS_URL] --profile b --use-device-code
29+
30+
>>> [CLI] auth login --host [DATABRICKS_URL] --use-device-code
31+
32+
>>> cache_keys
33+
[
34+
"a#device-code",
35+
"b#device-code",
36+
"[DATABRICKS_URL]",
37+
"[DATABRICKS_URL]#device-code",
38+
"test"
39+
]
40+
41+
>>> [CLI] auth login --host [DATABRICKS_URL] --profile b --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
42+
Profile b was successfully saved
43+
44+
>>> [CLI] auth login --host [DATABRICKS_URL] --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
45+
46+
>>> [CLI] auth login --host [DATABRICKS_URL] --profile a --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
47+
Profile a was successfully saved
48+
49+
>>> cache_keys
50+
[
51+
"a",
52+
"b",
53+
"[DATABRICKS_URL]",
54+
"test"
55+
]
56+
57+
>>> print_requests.py --oneline //oidc/v1/token
58+
{"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"}
59+
{"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"}
60+
{"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"}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
sethome "./home"
2+
3+
# The real server issues a new user code for every login, while the fake server
4+
# issues the same one. The script gives the second login its own user code.
5+
title "A second login for the same profile replaces the first\n"
6+
start_login LOG.first $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
7+
start_login LOG.second $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
8+
trace update_file.py home/.databricks/token-cache.json WDJBMJHT NEWRCODE
9+
trace cache_keys
10+
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
11+
trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code NEWR-CODE --confirmation-code FTFA-QQKY
12+
trace print_requests.py --oneline //oidc/v1/token
13+
14+
title "Logins for different targets are independent\n"
15+
start_login LOG.a $CLI auth login --host $DATABRICKS_HOST --profile a --use-device-code
16+
start_login LOG.b $CLI auth login --host $DATABRICKS_HOST --profile b --use-device-code
17+
start_login LOG.host $CLI auth login --host $DATABRICKS_HOST --use-device-code
18+
trace cache_keys
19+
trace $CLI auth login --host $DATABRICKS_HOST --profile b --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
20+
trace $CLI auth login --host $DATABRICKS_HOST --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
21+
trace $CLI auth login --host $DATABRICKS_HOST --profile a --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
22+
trace cache_keys
23+
trace print_requests.py --oneline //oidc/v1/token
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[resources]
2+
host = $DATABRICKS_HOST
3+
resources = https://resource.test
4+
auth_type = databricks-cli

acceptance/cmd/auth/login/device-code/wrong-code/out.test.toml renamed to acceptance/cmd/auth/login/device-code/non-interactive/errors/out.test.toml

File renamed without changes.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
2+
=== Device code login is disabled by default
3+
4+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
5+
Error: --use-device-code is experimental. Set DATABRICKS_CLI_DEVICE_CODE=true to enable it
6+
7+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
8+
Error: --use-device-code is experimental. Set DATABRICKS_CLI_DEVICE_CODE=true to enable it
9+
10+
=== Flags the non-interactive login cannot honor
11+
12+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --configure-cluster
13+
Error: --configure-cluster is not supported by the non-interactive device code login. Set cluster_id on the profile after the login
14+
15+
=== Missing host
16+
17+
>>> musterr [CLI] auth login --use-device-code
18+
Error: --use-device-code requires a workspace host. Pass it as an argument or with --host
19+
20+
>>> musterr [CLI] auth login --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
21+
Error: --use-device-code requires a workspace host. Pass it as an argument or with --host
22+
23+
=== Incomplete flags of the second step
24+
25+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --confirmation-code FTFA-QQKY
26+
Error: --confirmation-code requires --user-code
27+
28+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-MJHT
29+
Error: --user-code requires --confirmation-code
30+
31+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
32+
Error: --confirmation-code requires --use-device-code
33+
34+
=== Account host
35+
36+
>>> musterr [CLI] auth login --host https://accounts.cloud.databricks.test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
37+
Warn: [hostmetadata] failed to fetch host metadata for https://accounts.cloud.databricks.test, will skip for 1m0s
38+
Error: --use-device-code is only supported for workspace hosts
39+
40+
=== Profile scoped to resources
41+
42+
>>> musterr [CLI] auth login --profile resources --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
43+
Error: --use-device-code cannot be used with profile resources because its login is scoped to resources. Log in without --use-device-code
44+
45+
=== No pending login
46+
47+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
48+
Error: no pending device code login for profile test. Run the same command without --user-code and --confirmation-code to start a new login
49+
50+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
51+
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
52+
53+
>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
54+
55+
=== Wrong confirmation code
56+
57+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code AAAA-AAAA
58+
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
59+
60+
=== Rerun with the right confirmation code
61+
62+
>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code wdjb-mjht --confirmation-code ftfa-qqky
63+
Profile test was successfully saved
64+
65+
=== Rerun after the login was completed
66+
67+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
68+
Error: no pending device code login for profile test. Run the same command without --user-code and --confirmation-code to start a new login
69+
70+
=== Device code unknown to the server
71+
72+
>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code
73+
74+
>>> update_file.py home/.databricks/token-cache.json device-code-2 unknown-device-code
75+
76+
>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
77+
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
78+
79+
>>> cache_keys
80+
[
81+
"[DATABRICKS_URL]",
82+
"test"
83+
]
84+
85+
>>> print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token
86+
{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+all-apis"}
87+
{"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"}
88+
{"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"}
89+
{"method": "POST", "path": "/oidc/v1/device/auth", "raw_body": "client_id=databricks-cli&scope=offline_access+all-apis"}
90+
{"method": "POST", "path": "/oidc/v1/token", "raw_body": "client_id=databricks-cli&confirmation_code=FTFAQQKY&device_code=unknown-device-code&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
sethome "./home"
2+
3+
title "Device code login is disabled by default\n"
4+
(
5+
unset DATABRICKS_CLI_DEVICE_CODE
6+
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
7+
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
8+
)
9+
10+
title "Flags the non-interactive login cannot honor\n"
11+
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --configure-cluster
12+
13+
# The non-interactive login cannot answer the profile picker or the host prompt.
14+
title "Missing host\n"
15+
trace musterr $CLI auth login --use-device-code
16+
trace musterr $CLI auth login --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
17+
18+
title "Incomplete flags of the second step\n"
19+
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --confirmation-code FTFA-QQKY
20+
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-MJHT
21+
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
22+
23+
title "Account host\n"
24+
trace musterr $CLI auth login --host https://accounts.cloud.databricks.test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
25+
26+
envsubst < "./home/.databrickscfg.tmpl" > "./home/.databrickscfg"
27+
28+
title "Profile scoped to resources\n"
29+
trace musterr $CLI auth login --profile resources --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
30+
31+
title "No pending login\n"
32+
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
33+
trace musterr $CLI auth login --host $DATABRICKS_HOST --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
34+
35+
start_login LOG.wrong-code $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
36+
37+
title "Wrong confirmation code\n"
38+
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code AAAA-AAAA
39+
40+
# A wrong confirmation code leaves the device code valid, so the pending
41+
# login is kept.
42+
title "Rerun with the right confirmation code\n"
43+
trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code wdjb-mjht --confirmation-code ftfa-qqky
44+
45+
title "Rerun after the login was completed\n"
46+
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
47+
48+
# The server discards the device code when the login expires. The pending
49+
# login is then deleted.
50+
title "Device code unknown to the server\n"
51+
start_login LOG.expired $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code
52+
trace update_file.py home/.databricks/token-cache.json device-code-2 unknown-device-code
53+
trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY
54+
trace cache_keys
55+
56+
trace print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[scoped]
2+
host = $DATABRICKS_HOST
3+
scopes = sql
4+
client_id = databricks-cli
5+
auth_type = databricks-cli

0 commit comments

Comments
 (0)