From 74e22b92ebdd3970d40e5770d96337b6110f1895 Mon Sep 17 00:00:00 2001 From: Michael Thomson Date: Tue, 12 Dec 2023 01:37:32 +0000 Subject: [PATCH 01/11] use askpass instead of readline for oauth_flow_auth_code --- NEWS.md | 3 +++ R/oauth-flow-auth-code.R | 8 +++----- tests/testthat/test-oauth-flow-auth-code.R | 11 +++++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/NEWS.md b/NEWS.md index 6c8540b2..21c7c742 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,9 @@ * `req_template()` now works when you have a bare `:` in a template that uses "uri" style (#389). +* `oauth_flow_auth_code()` now uses `askpass::askpass()` instead of `readline()` + when prompting the user to enter character strings (@fh-mthomson, #406). + # httr2 1.0.0 ## Function lifecycle diff --git a/R/oauth-flow-auth-code.R b/R/oauth-flow-auth-code.R index aa6e6f58..3e91d2da 100644 --- a/R/oauth-flow-auth-code.R +++ b/R/oauth-flow-auth-code.R @@ -419,7 +419,8 @@ is_hosted_session <- function() { } oauth_flow_auth_code_read <- function(state) { - code <- trimws(readline("Enter authorization code or URL: ")) + check_installed("askpass") + code <- askpass::askpass("Enter authorization code or URL") if (is_string_url(code)) { # minimal setup where user copy & pastes a URL @@ -439,7 +440,7 @@ oauth_flow_auth_code_read <- function(state) { # Full manual approach, where the code and state are entered # independently. - new_state <- trimws(readline("Enter state parameter: ")) + new_state <- askpass::askpass("Enter state parameter") } if (!identical(state, new_state)) { @@ -486,6 +487,3 @@ oauth_flow_auth_code_fetch <- function(state) { body <- resp_body_json(resp) body$code } - -# Make base::readline() mockable -readline <- NULL diff --git a/tests/testthat/test-oauth-flow-auth-code.R b/tests/testthat/test-oauth-flow-auth-code.R index 0ffe0e6e..222c98e4 100644 --- a/tests/testthat/test-oauth-flow-auth-code.R +++ b/tests/testthat/test-oauth-flow-auth-code.R @@ -22,7 +22,8 @@ test_that("so-called 'hosted' sessions are detected correctly", { test_that("URL embedding authorisation code and state can be input manually", { local_mocked_bindings( - readline = function(prompt = "") "https://x.com?code=code&state=state" + askpass = function(prompt = "") "https://x.com?code=code&state=state", + .package = "askpass" ) expect_equal(oauth_flow_auth_code_read("state"), "code") expect_error(oauth_flow_auth_code_read("invalid"), "state does not match") @@ -32,7 +33,8 @@ test_that("JSON-encoded authorisation codes can be input manually", { input <- list(state = "state", code = "code") encoded <- openssl::base64_encode(jsonlite::toJSON(input)) local_mocked_bindings( - readline = function(prompt = "") encoded + askpass = function(prompt = "") encoded, + .package = "askpass" ) expect_equal(oauth_flow_auth_code_read("state"), "code") expect_error(oauth_flow_auth_code_read("invalid"), "state does not match") @@ -42,14 +44,15 @@ test_that("bare authorisation codes can be input manually", { state <- base64_url_rand(32) sent_code <- FALSE local_mocked_bindings( - readline = function(prompt = "") { + askpass = function(prompt = "") { if (sent_code) { state } else { sent_code <<- TRUE "zyx987" } - } + }, + .package = "askpass" ) expect_equal(oauth_flow_auth_code_read(state), "zyx987") expect_error(oauth_flow_auth_code_read("invalid"), "state does not match") From de863fd9377ffe9c5257a3346cdadad80b6349d5 Mon Sep 17 00:00:00 2001 From: Michael Thomson Date: Tue, 12 Dec 2023 02:37:38 +0000 Subject: [PATCH 02/11] use rstudioapi where possible --- NEWS.md | 5 +++-- R/oauth-flow-auth-code.R | 16 +++++++--------- R/oauth-flow-password.R | 16 ++++++++++++++-- tests/testthat/test-oauth-flow-auth-code.R | 11 ++++------- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/NEWS.md b/NEWS.md index 21c7c742..11c14a23 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,8 +10,9 @@ * `req_template()` now works when you have a bare `:` in a template that uses "uri" style (#389). -* `oauth_flow_auth_code()` now uses `askpass::askpass()` instead of `readline()` - when prompting the user to enter character strings (@fh-mthomson, #406). +* `oauth_flow_auth_code()` now uses `rstudioapi::askForPassword()` + (if in RStudio, `askpass::askpass()` otherwise) instead of `readline()` + when prompting the user to enter password / auth codes (@fh-mthomson, #406). # httr2 1.0.0 diff --git a/R/oauth-flow-auth-code.R b/R/oauth-flow-auth-code.R index 3e91d2da..c938ef7d 100644 --- a/R/oauth-flow-auth-code.R +++ b/R/oauth-flow-auth-code.R @@ -106,7 +106,6 @@ req_oauth_auth_code <- function(req, host_name = deprecated(), host_ip = deprecated(), port = deprecated()) { - redirect <- normalize_redirect_uri( redirect_uri = redirect_uri, host_name = host_name, @@ -139,9 +138,7 @@ oauth_flow_auth_code <- function(client, redirect_uri = oauth_redirect_uri(), host_name = deprecated(), host_ip = deprecated(), - port = deprecated() -) { - + port = deprecated()) { oauth_flow_check("authorization code", client, interactive = TRUE) redirect <- normalize_redirect_uri( @@ -204,7 +201,6 @@ normalize_redirect_uri <- function(redirect_uri, host_ip = deprecated(), port = deprecated(), error_call = caller_env()) { - parsed <- url_parse(redirect_uri) if (lifecycle::is_present(host_name)) { @@ -250,7 +246,6 @@ normalize_redirect_uri <- function(redirect_uri, localhost = localhost, can_fetch_code = can_fetch_oauth_code(redirect_uri) ) - } @@ -418,9 +413,12 @@ is_hosted_session <- function() { !grepl("localhost", Sys.getenv("RSTUDIO_HTTP_REFERER"), fixed = TRUE) } +is_rstudio_session <- function() { + !is.na(Sys.getenv("RSTUDIO_PROGRAM_MODE", unset = NA)) +} + oauth_flow_auth_code_read <- function(state) { - check_installed("askpass") - code <- askpass::askpass("Enter authorization code or URL") + code <- ask_user_prompt("Enter authorization code or URL: ") if (is_string_url(code)) { # minimal setup where user copy & pastes a URL @@ -440,7 +438,7 @@ oauth_flow_auth_code_read <- function(state) { # Full manual approach, where the code and state are entered # independently. - new_state <- askpass::askpass("Enter state parameter") + new_state <- ask_user_prompt("Enter state parameter: ") } if (!identical(state, new_state)) { diff --git a/R/oauth-flow-password.R b/R/oauth-flow-password.R index 88bf2f65..3338c48d 100644 --- a/R/oauth-flow-password.R +++ b/R/oauth-flow-password.R @@ -69,9 +69,21 @@ oauth_flow_password <- function(client, check_password <- function(password, call = caller_env()) { if (is.null(password)) { - check_installed("askpass", call = call) - password <- askpass::askpass() + password <- ask_user_prompt() } check_string(password, call = call) password } + +ask_user_prompt <- function(prompt = "Please enter your password: ") { + if (is_rstudio_session()) { + check_installed("rstudioapi") + result <- rstudioapi::askForPassword(prompt) + } else { + # use askpass as a fall back, which does not work when called non-interactively + # (including when knitting from RStudio) + check_installed("askpass") + result <- askpass::askpass(prompt) + } + result +} diff --git a/tests/testthat/test-oauth-flow-auth-code.R b/tests/testthat/test-oauth-flow-auth-code.R index 222c98e4..1c2b3b71 100644 --- a/tests/testthat/test-oauth-flow-auth-code.R +++ b/tests/testthat/test-oauth-flow-auth-code.R @@ -22,8 +22,7 @@ test_that("so-called 'hosted' sessions are detected correctly", { test_that("URL embedding authorisation code and state can be input manually", { local_mocked_bindings( - askpass = function(prompt = "") "https://x.com?code=code&state=state", - .package = "askpass" + ask_user_prompt = function(prompt = "") "https://x.com?code=code&state=state", ) expect_equal(oauth_flow_auth_code_read("state"), "code") expect_error(oauth_flow_auth_code_read("invalid"), "state does not match") @@ -33,8 +32,7 @@ test_that("JSON-encoded authorisation codes can be input manually", { input <- list(state = "state", code = "code") encoded <- openssl::base64_encode(jsonlite::toJSON(input)) local_mocked_bindings( - askpass = function(prompt = "") encoded, - .package = "askpass" + ask_user_prompt = function(prompt = "") encoded, ) expect_equal(oauth_flow_auth_code_read("state"), "code") expect_error(oauth_flow_auth_code_read("invalid"), "state does not match") @@ -44,15 +42,14 @@ test_that("bare authorisation codes can be input manually", { state <- base64_url_rand(32) sent_code <- FALSE local_mocked_bindings( - askpass = function(prompt = "") { + ask_user_prompt = function(prompt = "") { if (sent_code) { state } else { sent_code <<- TRUE "zyx987" } - }, - .package = "askpass" + } ) expect_equal(oauth_flow_auth_code_read(state), "zyx987") expect_error(oauth_flow_auth_code_read("invalid"), "state does not match") From f32cc86f7cee12374de8694f6ae2478d62168454 Mon Sep 17 00:00:00 2001 From: Michael Thomson Date: Tue, 12 Dec 2023 02:40:54 +0000 Subject: [PATCH 03/11] add rstudioapi to description --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index a0484041..ce933822 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -38,6 +38,7 @@ Suggests: jsonlite, knitr, rmarkdown, + rstudioapi, testthat (>= 3.1.8), tibble, webfakes, From 2b63237aa425faaca5e82e25fed2edf5c0117990 Mon Sep 17 00:00:00 2001 From: Michael Thomson Date: Tue, 12 Dec 2023 02:49:19 +0000 Subject: [PATCH 04/11] nit --- tests/testthat/test-oauth-flow-auth-code.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-oauth-flow-auth-code.R b/tests/testthat/test-oauth-flow-auth-code.R index 1c2b3b71..f931c1a3 100644 --- a/tests/testthat/test-oauth-flow-auth-code.R +++ b/tests/testthat/test-oauth-flow-auth-code.R @@ -22,7 +22,7 @@ test_that("so-called 'hosted' sessions are detected correctly", { test_that("URL embedding authorisation code and state can be input manually", { local_mocked_bindings( - ask_user_prompt = function(prompt = "") "https://x.com?code=code&state=state", + ask_user_prompt = function(prompt = "") "https://x.com?code=code&state=state" ) expect_equal(oauth_flow_auth_code_read("state"), "code") expect_error(oauth_flow_auth_code_read("invalid"), "state does not match") @@ -32,7 +32,7 @@ test_that("JSON-encoded authorisation codes can be input manually", { input <- list(state = "state", code = "code") encoded <- openssl::base64_encode(jsonlite::toJSON(input)) local_mocked_bindings( - ask_user_prompt = function(prompt = "") encoded, + ask_user_prompt = function(prompt = "") encoded ) expect_equal(oauth_flow_auth_code_read("state"), "code") expect_error(oauth_flow_auth_code_read("invalid"), "state does not match") From a3fe250f70f29fcdce0fccd7f5dbbe1ce3b3bbc7 Mon Sep 17 00:00:00 2001 From: Michael Thomson Date: Thu, 21 Dec 2023 00:48:17 +0000 Subject: [PATCH 05/11] use readline instead of askpass --- DESCRIPTION | 1 - NEWS.md | 4 ++-- R/oauth-flow-password.R | 7 +++---- vignettes/articles/wrapping-apis.Rmd | 4 ++-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ce933822..f56876e9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -28,7 +28,6 @@ Imports: vctrs (>= 0.6.3), withr Suggests: - askpass, bench, clipr, covr, diff --git a/NEWS.md b/NEWS.md index 11c14a23..0410f930 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,8 +11,8 @@ uses "uri" style (#389). * `oauth_flow_auth_code()` now uses `rstudioapi::askForPassword()` - (if in RStudio, `askpass::askpass()` otherwise) instead of `readline()` - when prompting the user to enter password / auth codes (@fh-mthomson, #406). + (if in RStudio, `readline()` otherwise) when prompting the user to + input auth codes (@fh-mthomson, #406). # httr2 1.0.0 diff --git a/R/oauth-flow-password.R b/R/oauth-flow-password.R index 3338c48d..65c098d9 100644 --- a/R/oauth-flow-password.R +++ b/R/oauth-flow-password.R @@ -80,10 +80,9 @@ ask_user_prompt <- function(prompt = "Please enter your password: ") { check_installed("rstudioapi") result <- rstudioapi::askForPassword(prompt) } else { - # use askpass as a fall back, which does not work when called non-interactively - # (including when knitting from RStudio) - check_installed("askpass") - result <- askpass::askpass(prompt) + # use readline as a fall back which works in R (or JupyterHub, see) + # https://github.com/r-lib/httr2/pull/410#issuecomment-1852721581 + result <- trimws(readline(prompt)) } result } diff --git a/vignettes/articles/wrapping-apis.Rmd b/vignettes/articles/wrapping-apis.Rmd index 36338878..8e9ef884 100644 --- a/vignettes/articles/wrapping-apis.Rmd +++ b/vignettes/articles/wrapping-apis.Rmd @@ -501,13 +501,13 @@ You can make this approach a little more user friendly by providing a helper tha ```{r} set_api_key <- function(key = NULL) { if (is.null(key)) { - key <- askpass::askpass("Please enter your API key") + key <- rstudioapi::askForPassword("Please enter your API key") } Sys.setenv("NYTIMES_KEY" = key) } ``` -Using askpass (or similar) here is good practice since you don't want to encourage the user to type their secret key into the console, as mentioned above. +Using `rstudioapi` (or similar) here is good practice since you don't want to encourage the user to type their secret key into the console, as mentioned above. It's a good idea to extend `get_api_key()` to automatically use your encrypted key to make it easier to write tests: From b569829de0f1c9b8da5092a4bd4b077509e5b51f Mon Sep 17 00:00:00 2001 From: Michael Thomson Date: Thu, 21 Dec 2023 00:50:56 +0000 Subject: [PATCH 06/11] nit --- R/oauth-flow-password.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/oauth-flow-password.R b/R/oauth-flow-password.R index 65c098d9..3761165b 100644 --- a/R/oauth-flow-password.R +++ b/R/oauth-flow-password.R @@ -80,8 +80,8 @@ ask_user_prompt <- function(prompt = "Please enter your password: ") { check_installed("rstudioapi") result <- rstudioapi::askForPassword(prompt) } else { - # use readline as a fall back which works in R (or JupyterHub, see) - # https://github.com/r-lib/httr2/pull/410#issuecomment-1852721581 + # use readline over askpass outside of RStudio IDE since it generalizes better to + # JupyterHub + Google Colab, see https://github.com/r-lib/httr2/pull/410#issuecomment-1852721581 result <- trimws(readline(prompt)) } result From 41d0e1072733c77a4ccd34507864e9f58473d735 Mon Sep 17 00:00:00 2001 From: Michael Thomson Date: Thu, 21 Dec 2023 23:00:08 +0000 Subject: [PATCH 07/11] move helpers to utils.R --- R/oauth-flow-auth-code.R | 28 ++++++------------- R/oauth-flow-password.R | 13 +-------- R/utils.R | 31 ++++++++++++++++++++++ tests/testthat/test-oauth-flow-auth-code.R | 6 ++--- 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/R/oauth-flow-auth-code.R b/R/oauth-flow-auth-code.R index c938ef7d..0e1c0572 100644 --- a/R/oauth-flow-auth-code.R +++ b/R/oauth-flow-auth-code.R @@ -106,6 +106,7 @@ req_oauth_auth_code <- function(req, host_name = deprecated(), host_ip = deprecated(), port = deprecated()) { + redirect <- normalize_redirect_uri( redirect_uri = redirect_uri, host_name = host_name, @@ -138,7 +139,9 @@ oauth_flow_auth_code <- function(client, redirect_uri = oauth_redirect_uri(), host_name = deprecated(), host_ip = deprecated(), - port = deprecated()) { + port = deprecated() + ) { + oauth_flow_check("authorization code", client, interactive = TRUE) redirect <- normalize_redirect_uri( @@ -201,6 +204,7 @@ normalize_redirect_uri <- function(redirect_uri, host_ip = deprecated(), port = deprecated(), error_call = caller_env()) { + parsed <- url_parse(redirect_uri) if (lifecycle::is_present(host_name)) { @@ -246,6 +250,7 @@ normalize_redirect_uri <- function(redirect_uri, localhost = localhost, can_fetch_code = can_fetch_oauth_code(redirect_uri) ) + } @@ -398,27 +403,10 @@ oauth_flow_auth_code_pkce <- function() { ) } -# Try to determine whether we can redirect the user's browser to a server on -# localhost, which isn't possible if we are running on a hosted platform. -# -# Currently this detects RStudio Server, Posit Workbench, and Google Colab. It -# is based on the strategy pioneered by the {gargle} package. -is_hosted_session <- function() { - if (nzchar(Sys.getenv("COLAB_RELEASE_TAG"))) { - return(TRUE) - } - # If RStudio Server or Posit Workbench is running locally (which is possible, - # though unusual), it's not acting as a hosted environment. - Sys.getenv("RSTUDIO_PROGRAM_MODE") == "server" && - !grepl("localhost", Sys.getenv("RSTUDIO_HTTP_REFERER"), fixed = TRUE) -} -is_rstudio_session <- function() { - !is.na(Sys.getenv("RSTUDIO_PROGRAM_MODE", unset = NA)) -} oauth_flow_auth_code_read <- function(state) { - code <- ask_user_prompt("Enter authorization code or URL: ") + code <- prompt_user("Enter authorization code or URL: ") if (is_string_url(code)) { # minimal setup where user copy & pastes a URL @@ -438,7 +426,7 @@ oauth_flow_auth_code_read <- function(state) { # Full manual approach, where the code and state are entered # independently. - new_state <- ask_user_prompt("Enter state parameter: ") + new_state <- prompt_user("Enter state parameter: ") } if (!identical(state, new_state)) { diff --git a/R/oauth-flow-password.R b/R/oauth-flow-password.R index 3761165b..60d421f4 100644 --- a/R/oauth-flow-password.R +++ b/R/oauth-flow-password.R @@ -69,20 +69,9 @@ oauth_flow_password <- function(client, check_password <- function(password, call = caller_env()) { if (is.null(password)) { - password <- ask_user_prompt() + password <- prompt_user() } check_string(password, call = call) password } -ask_user_prompt <- function(prompt = "Please enter your password: ") { - if (is_rstudio_session()) { - check_installed("rstudioapi") - result <- rstudioapi::askForPassword(prompt) - } else { - # use readline over askpass outside of RStudio IDE since it generalizes better to - # JupyterHub + Google Colab, see https://github.com/r-lib/httr2/pull/410#issuecomment-1852721581 - result <- trimws(readline(prompt)) - } - result -} diff --git a/R/utils.R b/R/utils.R index ce27537a..17ca2a54 100644 --- a/R/utils.R +++ b/R/utils.R @@ -281,3 +281,34 @@ create_progress_bar <- function(total, done = function() cli::cli_progress_done(id = id) ) } + +prompt_user <- function(prompt = "Please enter your password: ") { + if (is_rstudio_session()) { + check_installed("rstudioapi") + result <- rstudioapi::askForPassword(prompt) + } else { + # use readline over askpass outside of RStudio IDE since it generalizes better to + # JupyterHub + Google Colab, see https://github.com/r-lib/httr2/pull/410#issuecomment-1852721581 + result <- trimws(readline(prompt)) + } + result +} + +is_rstudio_session <- function() { + !is.na(Sys.getenv("RSTUDIO_PROGRAM_MODE", unset = NA)) +} + +# Try to determine whether we can redirect the user's browser to a server on +# localhost, which isn't possible if we are running on a hosted platform. +# +# Currently this detects RStudio Server, Posit Workbench, and Google Colab. It +# is based on the strategy pioneered by the {gargle} package. +is_hosted_session <- function() { + if (nzchar(Sys.getenv("COLAB_RELEASE_TAG"))) { + return(TRUE) + } + # If RStudio Server or Posit Workbench is running locally (which is possible, + # though unusual), it's not acting as a hosted environment. + Sys.getenv("RSTUDIO_PROGRAM_MODE") == "server" && + !grepl("localhost", Sys.getenv("RSTUDIO_HTTP_REFERER"), fixed = TRUE) +} diff --git a/tests/testthat/test-oauth-flow-auth-code.R b/tests/testthat/test-oauth-flow-auth-code.R index f931c1a3..514f7fa6 100644 --- a/tests/testthat/test-oauth-flow-auth-code.R +++ b/tests/testthat/test-oauth-flow-auth-code.R @@ -22,7 +22,7 @@ test_that("so-called 'hosted' sessions are detected correctly", { test_that("URL embedding authorisation code and state can be input manually", { local_mocked_bindings( - ask_user_prompt = function(prompt = "") "https://x.com?code=code&state=state" + prompt_user = function(prompt = "") "https://x.com?code=code&state=state" ) expect_equal(oauth_flow_auth_code_read("state"), "code") expect_error(oauth_flow_auth_code_read("invalid"), "state does not match") @@ -32,7 +32,7 @@ test_that("JSON-encoded authorisation codes can be input manually", { input <- list(state = "state", code = "code") encoded <- openssl::base64_encode(jsonlite::toJSON(input)) local_mocked_bindings( - ask_user_prompt = function(prompt = "") encoded + prompt_user = function(prompt = "") encoded ) expect_equal(oauth_flow_auth_code_read("state"), "code") expect_error(oauth_flow_auth_code_read("invalid"), "state does not match") @@ -42,7 +42,7 @@ test_that("bare authorisation codes can be input manually", { state <- base64_url_rand(32) sent_code <- FALSE local_mocked_bindings( - ask_user_prompt = function(prompt = "") { + prompt_user = function(prompt = "") { if (sent_code) { state } else { From d72bff25dd33d89861c7ca0a6c54d951218b068a Mon Sep 17 00:00:00 2001 From: Michael Thomson Date: Thu, 21 Dec 2023 23:11:18 +0000 Subject: [PATCH 08/11] add reason --- R/utils.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utils.R b/R/utils.R index 17ca2a54..fd79e44b 100644 --- a/R/utils.R +++ b/R/utils.R @@ -284,7 +284,7 @@ create_progress_bar <- function(total, prompt_user <- function(prompt = "Please enter your password: ") { if (is_rstudio_session()) { - check_installed("rstudioapi") + check_installed("rstudioapi", reason = "to ask user for inputs.") result <- rstudioapi::askForPassword(prompt) } else { # use readline over askpass outside of RStudio IDE since it generalizes better to From dfc0ef16eeca87a74b618be495728ca314f6d81f Mon Sep 17 00:00:00 2001 From: Michael Thomson Date: Thu, 21 Dec 2023 23:14:50 +0000 Subject: [PATCH 09/11] whitespace --- R/oauth-flow-auth-code.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/oauth-flow-auth-code.R b/R/oauth-flow-auth-code.R index 0e1c0572..635d33a2 100644 --- a/R/oauth-flow-auth-code.R +++ b/R/oauth-flow-auth-code.R @@ -140,7 +140,7 @@ oauth_flow_auth_code <- function(client, host_name = deprecated(), host_ip = deprecated(), port = deprecated() - ) { +) { oauth_flow_check("authorization code", client, interactive = TRUE) From bfb819776eb6f9249a3caa94fd8a272c06caf8ae Mon Sep 17 00:00:00 2001 From: Michael Thomson Date: Sat, 16 Mar 2024 02:55:08 +0000 Subject: [PATCH 10/11] add informative error message for is_interactive --- R/utils.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/utils.R b/R/utils.R index fd79e44b..46a8c282 100644 --- a/R/utils.R +++ b/R/utils.R @@ -286,11 +286,14 @@ prompt_user <- function(prompt = "Please enter your password: ") { if (is_rstudio_session()) { check_installed("rstudioapi", reason = "to ask user for inputs.") result <- rstudioapi::askForPassword(prompt) - } else { + } else if (rlang::is_interactive()) { # use readline over askpass outside of RStudio IDE since it generalizes better to # JupyterHub + Google Colab, see https://github.com/r-lib/httr2/pull/410#issuecomment-1852721581 result <- trimws(readline(prompt)) + } else { + cli::cli_abort("Unable to obtain user input in a non-interactive session.") } + result } From 297b8e020edf608313d50014ce5de667fcd8102e Mon Sep 17 00:00:00 2001 From: Michael Thomson <56164984+fh-mthomson@users.noreply.github.com> Date: Fri, 15 Mar 2024 20:02:48 -0700 Subject: [PATCH 11/11] Update vignettes/articles/wrapping-apis.Rmd Co-authored-by: Hadley Wickham --- vignettes/articles/wrapping-apis.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/articles/wrapping-apis.Rmd b/vignettes/articles/wrapping-apis.Rmd index 8e9ef884..ddb7b309 100644 --- a/vignettes/articles/wrapping-apis.Rmd +++ b/vignettes/articles/wrapping-apis.Rmd @@ -507,7 +507,7 @@ set_api_key <- function(key = NULL) { } ``` -Using `rstudioapi` (or similar) here is good practice since you don't want to encourage the user to type their secret key into the console, as mentioned above. +Using rstudioapi (or similar) here is good practice since you don't want to encourage the user to type their secret key into the console, as mentioned above. It's a good idea to extend `get_api_key()` to automatically use your encrypted key to make it easier to write tests: