diff --git a/acceptance/cmd/auth/login/device-code/nominal/script b/acceptance/cmd/auth/login/device-code/nominal/script deleted file mode 100644 index f1d6c04f59a..00000000000 --- a/acceptance/cmd/auth/login/device-code/nominal/script +++ /dev/null @@ -1,20 +0,0 @@ -sethome "./home" - -title "Login with a host\n" -printf "FTFA-QQKY\n" | trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code -trace $CLI auth token --profile test | gron.py | grep access_token -trace $CLI auth profiles - -title "Login again with the host from the saved profile\n" -printf "FTFA-QQKY\n" | trace $CLI auth login --profile test --use-device-code -trace $CLI auth token --profile test | gron.py | grep access_token -trace print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token - -# The token gets the scopes requested at device authorization, so --scopes is -# sent there and not with the token request. -title "Login with custom scopes\n" -printf "FTFA-QQKY\n" | trace $CLI auth login --host $DATABRICKS_HOST --profile scoped --use-device-code --scopes sql -trace print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token - -# Track the .databrickscfg file that was created to surface changes. -mv "./home/.databrickscfg" "./out.databrickscfg" diff --git a/acceptance/cmd/auth/login/device-code/nominal/test.toml b/acceptance/cmd/auth/login/device-code/nominal/test.toml deleted file mode 100644 index 159efe02696..00000000000 --- a/acceptance/cmd/auth/login/device-code/nominal/test.toml +++ /dev/null @@ -1 +0,0 @@ -RecordRequests = true diff --git a/acceptance/cmd/auth/login/device-code/nominal/out.test.toml b/acceptance/cmd/auth/login/device-code/non-interactive/concurrent/out.test.toml similarity index 100% rename from acceptance/cmd/auth/login/device-code/nominal/out.test.toml rename to acceptance/cmd/auth/login/device-code/non-interactive/concurrent/out.test.toml diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/concurrent/output.txt b/acceptance/cmd/auth/login/device-code/non-interactive/concurrent/output.txt new file mode 100644 index 00000000000..641900bd214 --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/concurrent/output.txt @@ -0,0 +1,60 @@ + +=== 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 + +>>> update_file.py home/.databricks/token-cache.json WDJBMJHT NEWRCODE + +>>> cache_keys +[ + "test#device-code" +] + +>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY +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 + +>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code NEWR-CODE --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-MJHT --confirmation-code FTFA-QQKY +Profile b was successfully saved + +>>> [CLI] auth login --host [DATABRICKS_URL] --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY + +>>> [CLI] auth login --host [DATABRICKS_URL] --profile a --use-device-code --user-code WDJB-MJHT --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"} diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/concurrent/script b/acceptance/cmd/auth/login/device-code/non-interactive/concurrent/script new file mode 100644 index 00000000000..97ade6f94d2 --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/concurrent/script @@ -0,0 +1,23 @@ +sethome "./home" + +# The real server issues a new user code for every login, while the fake server +# issues the same one. The script gives the second login its own user code. +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 update_file.py home/.databricks/token-cache.json WDJBMJHT NEWRCODE +trace cache_keys +trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY +trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code NEWR-CODE --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-MJHT --confirmation-code FTFA-QQKY +trace $CLI auth login --host $DATABRICKS_HOST --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY +trace $CLI auth login --host $DATABRICKS_HOST --profile a --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY +trace cache_keys +trace print_requests.py --oneline //oidc/v1/token diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/errors/home/.databrickscfg.tmpl b/acceptance/cmd/auth/login/device-code/non-interactive/errors/home/.databrickscfg.tmpl new file mode 100644 index 00000000000..3017709a305 --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/errors/home/.databrickscfg.tmpl @@ -0,0 +1,4 @@ +[resources] +host = $DATABRICKS_HOST +resources = https://resource.test +auth_type = databricks-cli diff --git a/acceptance/cmd/auth/login/device-code/wrong-code/out.test.toml b/acceptance/cmd/auth/login/device-code/non-interactive/errors/out.test.toml similarity index 100% rename from acceptance/cmd/auth/login/device-code/wrong-code/out.test.toml rename to acceptance/cmd/auth/login/device-code/non-interactive/errors/out.test.toml diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/errors/output.txt b/acceptance/cmd/auth/login/device-code/non-interactive/errors/output.txt new file mode 100644 index 00000000000..e293a7f47c2 --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/errors/output.txt @@ -0,0 +1,98 @@ + +=== 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-MJHT --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-MJHT --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-MJHT +Error: --user-code requires --confirmation-code + +>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --user-code WDJB-MJHT --confirmation-code FTFA-QQKY +Error: --confirmation-code requires --use-device-code + +=== Account host + +>>> musterr [CLI] auth login --host https://accounts.cloud.databricks.test --use-device-code --user-code WDJB-MJHT --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-MJHT --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-MJHT --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-MJHT --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-MJHT --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-mjht --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-MJHT --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 + +>>> update_file.py home/.databricks/token-cache.json device-code-2 unknown-device-code + +>>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-MJHT --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-MJHT --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=unknown-device-code&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"} diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/errors/script b/acceptance/cmd/auth/login/device-code/non-interactive/errors/script new file mode 100644 index 00000000000..69517fddaf2 --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/errors/script @@ -0,0 +1,62 @@ +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-MJHT --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-MJHT --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-MJHT +trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --user-code WDJB-MJHT --confirmation-code FTFA-QQKY + +title "Account host\n" +trace musterr $CLI auth login --host https://accounts.cloud.databricks.test --use-device-code --user-code WDJB-MJHT --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-MJHT --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-MJHT --confirmation-code FTFA-QQKY +trace musterr $CLI auth login --host $DATABRICKS_HOST --use-device-code --user-code WDJB-MJHT --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-MJHT --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-mjht --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-MJHT --confirmation-code FTFA-QQKY + +# The server discards the device code when the login expires. 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 update_file.py home/.databricks/token-cache.json device-code-2 unknown-device-code +trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-MJHT --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-MJHT --confirmation-code FTFA-QQKY + +trace print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/flags/home/.databrickscfg.tmpl b/acceptance/cmd/auth/login/device-code/non-interactive/flags/home/.databrickscfg.tmpl new file mode 100644 index 00000000000..d8e7fb8ab03 --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/flags/home/.databrickscfg.tmpl @@ -0,0 +1,5 @@ +[scoped] +host = $DATABRICKS_HOST +scopes = sql +client_id = databricks-cli +auth_type = databricks-cli diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/flags/out.databrickscfg b/acceptance/cmd/auth/login/device-code/non-interactive/flags/out.databrickscfg new file mode 100644 index 00000000000..18fafc1c622 --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/flags/out.databrickscfg @@ -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 diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/flags/out.test.toml b/acceptance/cmd/auth/login/device-code/non-interactive/flags/out.test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/flags/out.test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/flags/output.txt b/acceptance/cmd/auth/login/device-code/non-interactive/flags/output.txt new file mode 100644 index 00000000000..a39e6a70953 --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/flags/output.txt @@ -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-MJHT + +Once finished, complete the login by running the same command with +--user-code WDJB-MJHT and --confirmation-code set to the confirmation code +provided in your browser. + +>>> [CLI] auth login --profile scoped --use-device-code --user-code WDJB-MJHT --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-MJHT + +Once finished, complete the login by running the same command with +--user-code WDJB-MJHT 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-MJHT --confirmation-code FTFA-QQKY +Profile serverless was successfully saved diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/flags/script b/acceptance/cmd/auth/login/device-code/non-interactive/flags/script new file mode 100644 index 00000000000..4824729081c --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/flags/script @@ -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-MJHT --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-MJHT --confirmation-code FTFA-QQKY + +mv "./home/.databrickscfg" "./out.databrickscfg" diff --git a/acceptance/cmd/auth/login/device-code/nominal/out.databrickscfg b/acceptance/cmd/auth/login/device-code/non-interactive/nominal/out.databrickscfg similarity index 68% rename from acceptance/cmd/auth/login/device-code/nominal/out.databrickscfg rename to acceptance/cmd/auth/login/device-code/non-interactive/nominal/out.databrickscfg index 02b3737c466..cf14685849c 100644 --- a/acceptance/cmd/auth/login/device-code/nominal/out.databrickscfg +++ b/acceptance/cmd/auth/login/device-code/non-interactive/nominal/out.databrickscfg @@ -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 diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/nominal/out.test.toml b/acceptance/cmd/auth/login/device-code/non-interactive/nominal/out.test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/nominal/out.test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/cmd/auth/login/device-code/nominal/output.txt b/acceptance/cmd/auth/login/device-code/non-interactive/nominal/output.txt similarity index 52% rename from acceptance/cmd/auth/login/device-code/nominal/output.txt rename to acceptance/cmd/auth/login/device-code/non-interactive/nominal/output.txt index 2216533b304..5a70dc2fd0c 100644 --- a/acceptance/cmd/auth/login/device-code/nominal/output.txt +++ b/acceptance/cmd/auth/login/device-code/non-interactive/nominal/output.txt @@ -1,5 +1,40 @@ -=== Login with a host +=== Start the login without a profile + +>>> [CLI] auth login --host [DATABRICKS_URL] --use-device-code +Using a browser on another device, visit: +[DATABRICKS_URL]/device/verification + +And enter the code: +WDJB-MJHT + +Once finished, complete the login by running the same command with +--user-code WDJB-MJHT and --confirmation-code set to the confirmation code +provided in your browser. + +=== The first step stores the device code and saves no profile + +>>> find.py home +home/.databricks/token-cache.json + +>>> cache_keys +[ + "[DATABRICKS_URL]#device-code" +] + +=== Confirm the login + +>>> [CLI] auth login --host [DATABRICKS_URL] --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY + +>>> [CLI] auth token --host [DATABRICKS_URL] +json.access_token = "oauth-token"; + +>>> cache_keys +[ + "[DATABRICKS_URL]" +] + +=== Start the login with a profile >>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code Using a browser on another device, visit: @@ -8,8 +43,14 @@ Using a browser on another device, visit: And enter the code: WDJB-MJHT -Once finished, enter the confirmation code provided in your browser. -Confirmation code: Profile test was successfully saved +Once finished, complete the login by running the same command with +--user-code WDJB-MJHT and --confirmation-code set to the confirmation code +provided in your browser. + +=== Confirm the login + +>>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY +Profile test was successfully saved >>> [CLI] auth token --profile test json.access_token = "oauth-token"; @@ -18,7 +59,7 @@ json.access_token = "oauth-token"; Name Host Valid test (Default) [DATABRICKS_URL] YES -=== Login again with the host from the saved profile +=== Start the login again with the host from the saved profile >>> [CLI] auth login --profile test --use-device-code Using a browser on another device, visit: @@ -27,30 +68,26 @@ Using a browser on another device, visit: And enter the code: WDJB-MJHT -Once finished, enter the confirmation code provided in your browser. -Confirmation code: Profile test was successfully saved +Once finished, complete the login by running the same command with +--user-code WDJB-MJHT and --confirmation-code set to the confirmation code +provided in your browser. >>> [CLI] auth token --profile test json.access_token = "oauth-token"; +>>> [CLI] auth login --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY +Profile test was successfully saved + +>>> cache_keys +[ + "[DATABRICKS_URL]", + "test" +] + >>> 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=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"} - -=== Login with custom scopes - ->>> [CLI] auth login --host [DATABRICKS_URL] --profile scoped --use-device-code --scopes sql -Using a browser on another device, visit: -[DATABRICKS_URL]/device/verification - -And enter the code: -WDJB-MJHT - -Once finished, enter the confirmation code provided in your browser. -Confirmation code: 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/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-3&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code"} diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/nominal/script b/acceptance/cmd/auth/login/device-code/non-interactive/nominal/script new file mode 100644 index 00000000000..2f00d282b27 --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/nominal/script @@ -0,0 +1,35 @@ +sethome "./home" + +# Acceptance tests run the CLI without a terminal on stdin, so device code +# login runs non-interactively. +title "Start the login without a profile\n" +trace $CLI auth login --host $DATABRICKS_HOST --use-device-code + +title "The first step stores the device code and saves no profile\n" +trace find.py home +trace cache_keys + +title "Confirm the login\n" +trace $CLI auth login --host $DATABRICKS_HOST --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY +trace $CLI auth token --host $DATABRICKS_HOST | gron.py | grep access_token +trace cache_keys + +title "Start the login with a profile\n" +trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code + +title "Confirm the login\n" +trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY +trace $CLI auth token --profile test | gron.py | grep access_token +trace $CLI auth profiles + +# The pending login is stored next to the token, so the token keeps working +# until the login is confirmed. +title "Start the login again with the host from the saved profile\n" +trace $CLI auth login --profile test --use-device-code +trace $CLI auth token --profile test | gron.py | grep access_token +trace $CLI auth login --profile test --use-device-code --user-code WDJB-MJHT --confirmation-code FTFA-QQKY +trace cache_keys +trace print_requests.py --oneline //oidc/v1/device/auth //oidc/v1/token + +# Track the .databrickscfg file that was created to surface changes. +mv "./home/.databrickscfg" "./out.databrickscfg" diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/script.prepare b/acceptance/cmd/auth/login/device-code/non-interactive/script.prepare new file mode 100644 index 00000000000..447c920c748 --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/script.prepare @@ -0,0 +1,13 @@ +# cache_keys lists the keys in the plaintext token cache. +cache_keys() { + jq -S '.tokens | keys' "./home/.databricks/token-cache.json" +} + +# start_login traces the first step in "$@" and saves its output to file $1, +# for steps whose output was already shown. +start_login() { + local log="$1" + shift + >&2 printf "\n>>> %s\n" "$*" + "$@" &> "$log" +} diff --git a/acceptance/cmd/auth/login/device-code/non-interactive/test.toml b/acceptance/cmd/auth/login/device-code/non-interactive/test.toml new file mode 100644 index 00000000000..f493574d1e4 --- /dev/null +++ b/acceptance/cmd/auth/login/device-code/non-interactive/test.toml @@ -0,0 +1,3 @@ +# A dedicated server issues device codes in a known order, so that the scripts +# can confirm them. +RecordRequests = true diff --git a/acceptance/cmd/auth/login/device-code/unsupported-host/output.txt b/acceptance/cmd/auth/login/device-code/unsupported-host/output.txt index 7875f49ed6b..5d327ce2db2 100644 --- a/acceptance/cmd/auth/login/device-code/unsupported-host/output.txt +++ b/acceptance/cmd/auth/login/device-code/unsupported-host/output.txt @@ -2,7 +2,7 @@ === No host >>> musterr [CLI] auth login --profile new-profile --use-device-code -Error: the command is being run in a non-interactive environment, please specify a host using --host +Error: --use-device-code requires a workspace host. Pass it as an argument or with --host === Account host diff --git a/acceptance/cmd/auth/login/device-code/unsupported-host/script b/acceptance/cmd/auth/login/device-code/unsupported-host/script index d744aec1709..2d0a8949c8e 100644 --- a/acceptance/cmd/auth/login/device-code/unsupported-host/script +++ b/acceptance/cmd/auth/login/device-code/unsupported-host/script @@ -4,7 +4,7 @@ title "No host\n" # The profile does not exist yet and no host is given. auth login does not read # DATABRICKS_HOST, so the usual login would fall back to the browser-based # discovery flow. Device code login skips it and asks for the host instead, -# which fails without a terminal. +# which the non-interactive login cannot do. trace musterr $CLI auth login --profile new-profile --use-device-code envsubst < "./home/.databrickscfg.tmpl" > "./home/.databrickscfg" diff --git a/acceptance/cmd/auth/login/device-code/wrong-code/output.txt b/acceptance/cmd/auth/login/device-code/wrong-code/output.txt deleted file mode 100644 index 2ba7f3ea23e..00000000000 --- a/acceptance/cmd/auth/login/device-code/wrong-code/output.txt +++ /dev/null @@ -1,40 +0,0 @@ - -=== Login fails when no confirmation code is provided - ->>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code -Using a browser on another device, visit: -[DATABRICKS_URL]/device/verification - -And enter the code: -WDJB-MJHT - -Once finished, enter the confirmation code provided in your browser. -Confirmation code: Error: reading confirmation code: EOF - -=== Login fails after 3 wrong confirmation codes - ->>> musterr [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code -Using a browser on another device, visit: -[DATABRICKS_URL]/device/verification - -And enter the code: -WDJB-MJHT - -Once finished, enter the confirmation code provided in your browser. -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (2 attempts left). -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (1 attempt left). -Confirmation code: Error: Invalid confirmation code (error code: invalid_request). Start login from scratch - -=== Login succeeds after re-entering the confirmation code - ->>> [CLI] auth login --host [DATABRICKS_URL] --profile test --use-device-code -Using a browser on another device, visit: -[DATABRICKS_URL]/device/verification - -And enter the code: -WDJB-MJHT - -Once finished, enter the confirmation code provided in your browser. -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (2 attempts left). -Confirmation code: Invalid confirmation code (error code: invalid_request). Enter the confirmation code again (1 attempt left). -Confirmation code: Profile test was successfully saved diff --git a/acceptance/cmd/auth/login/device-code/wrong-code/script b/acceptance/cmd/auth/login/device-code/wrong-code/script deleted file mode 100644 index 3adb0de81a3..00000000000 --- a/acceptance/cmd/auth/login/device-code/wrong-code/script +++ /dev/null @@ -1,13 +0,0 @@ -sethome "./home" - -title "Login fails when no confirmation code is provided\n" -trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code < /dev/null - -title "Login fails after 3 wrong confirmation codes\n" -printf "AAAA-AAAA\nBBBB-BBBB\nCCCC-CCCC\n" | trace musterr $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code - -# A wrong confirmation code leaves the device code valid, so the user can -# enter it again. A blank line counts as a wrong confirmation code. -# The confirmation code is accepted the way the browser displays it, in any case. -title "Login succeeds after re-entering the confirmation code\n" -printf "AAAA-AAAA\n\nftfa-qqky\n" | trace $CLI auth login --host $DATABRICKS_HOST --profile test --use-device-code diff --git a/acceptance/cmd/auth/logout/pending-device-code-login/out.test.toml b/acceptance/cmd/auth/logout/pending-device-code-login/out.test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/cmd/auth/logout/pending-device-code-login/out.test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/cmd/auth/logout/pending-device-code-login/output.txt b/acceptance/cmd/auth/logout/pending-device-code-login/output.txt new file mode 100644 index 00000000000..b347f1321d4 --- /dev/null +++ b/acceptance/cmd/auth/logout/pending-device-code-login/output.txt @@ -0,0 +1,19 @@ + +=== Logout and delete dev +>>> [CLI] auth logout --profile dev --auto-approve --delete +Logged out of and deleted profile "dev". + +=== Token cache keys after logout — the host keys are kept, staging shares them +[ + "[DATABRICKS_URL]", + "[DATABRICKS_URL]#device-code", + "staging", + "staging#device-code" +] + +=== Logout staging +>>> [CLI] auth logout --profile staging --auto-approve +Logged out of profile "staging". Use --delete to also remove it from the config file. + +=== Token cache keys after logout — no profile uses the host anymore +[] diff --git a/acceptance/cmd/auth/logout/pending-device-code-login/script b/acceptance/cmd/auth/logout/pending-device-code-login/script new file mode 100644 index 00000000000..1d62f2cb572 --- /dev/null +++ b/acceptance/cmd/auth/logout/pending-device-code-login/script @@ -0,0 +1,38 @@ +sethome "./home" + +cat > "./home/.databrickscfg" < "./home/.databricks/token-cache.json" <