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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Imports:
cli (>= 3.0.0),
fs (>= 1.3.1),
glue (>= 1.3.0),
httr (>= 1.4.0),
httr (>= 1.4.5),
jsonlite,
lifecycle,
openssl,
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export(gargle_map_cli)
export(gargle_oauth_cache)
export(gargle_oauth_client)
export(gargle_oauth_client_from_json)
export(gargle_oauth_client_type)
export(gargle_oauth_email)
export(gargle_oauth_sitrep)
export(gargle_oob_default)
Expand Down
10 changes: 10 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ Changes of note:

gargle is better able to detect when it's running on Posit Workbench or RStudio Server, e.g., in a subprocess.

* `"gargle_oauth_client_type"` is a new global option that can be used to
express a preference for the OAuth client type ("installed" or "web").
In the context of out-of-band (OOB) auth, an "installed" client type leads to the conventional OOB flow (only available for GCP projects in testing mode) and a "web" client leads to the new pseudo-OOB flow.

* `gargle_oauth_client_type()` is a new function that returns either "installed"
or "web". It returns the global option `"gargle_oauth_client_type"`, if defined.
If the option is not defined, returns "web" on RStudio Server, Posit Workbench, Posit Cloud, or Google Colab, and "installed" otherwise.

* `credentials_user_oauth2()` now works in Google Colab, by giving Colab the same treatment as RStudio Server, Posit Workbench, and Posit Cloud.

# gargle 1.3.0

## (Partial) deprecation out-of-band (OOB) auth flow
Expand Down
12 changes: 10 additions & 2 deletions R/Gargle-class.R
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ gargle2.0_token <- function(email = gargle_oauth_email(),
params$oob_value <- select_pseudo_oob_value(app$redirect_uris)
}

# explicitly declare Google Colab to be an interactive environment
if (is_google_colab()) {
withr::local_options(rlang_interactive = TRUE)
}

if (!identical(client_type, "web")) {
# don't use the new client type!
# specifically, for an installed / desktop app client, we want to use httr's
Expand Down Expand Up @@ -250,7 +255,7 @@ Gargle2.0 <- R6::R6Class("Gargle2.0", inherit = httr::Token2.0, list(
init_credentials = function() {
gargle_debug("initiating new token")
if (is_interactive()) {
if (!isTRUE(self$params$use_oob) && !is_rstudio_server()) {
if (!isTRUE(self$params$use_oob)) {
encourage_httpuv()
}
self$credentials <- init_oauth2.0(
Expand All @@ -270,7 +275,10 @@ Gargle2.0 <- R6::R6Class("Gargle2.0", inherit = httr::Token2.0, list(
))

encourage_httpuv <- function() {
if (!is_interactive() || isTRUE(is_installed("httpuv"))) {
if (!is_interactive() ||
isTRUE(is_installed("httpuv")) ||
is_rstudio_server() ||
is_google_colab()) {
return(invisible())
}
local_gargle_verbosity("info")
Expand Down
4 changes: 2 additions & 2 deletions R/gargle-oauth-client.R
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ list_redact <- function(x, names, case_sensitive = TRUE) {
#' }
gargle_client <- function(type = NULL) {
if (is.null(type) || is.na(type)) {
type <- if(is_rstudio_server()) "web" else "installed"
type <- gargle_oauth_client_type()
}
check_string(type)
type <- arg_match(type, values = c("installed", "web"))
Expand All @@ -218,7 +218,7 @@ tidyverse_client <- function(type = NULL) {
check_permitted_package(parent.frame())

if (is.null(type) || is.na(type)) {
type <- if(is_rstudio_server()) "web" else "installed"
type <- gargle_oauth_client_type()
}
check_string(type)
type <- arg_match(type, values = c("installed", "web"))
Expand Down
32 changes: 27 additions & 5 deletions R/gargle-package.R
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ gargle_oauth_email <- function() {
#' @export
#' @section `gargle_oob_default`:
#' `gargle_oob_default()` returns `TRUE` unconditionally on RStudio Server,
#' Posit Workbench, or Posit Cloud, since it is not possible to launch a local
#' web server in these contexts. In this case, for the final step of the OAuth
#' dance, the user is redirected to a specific URL where they must copy a code
#' and paste it back into the R session.
#' Posit Workbench, Posit Cloud, or Google Colab, since it is not possible to
#' launch a local web server in these contexts. In this case, for the final step
#' of the OAuth dance, the user is redirected to a specific URL where they must
#' copy a code and paste it back into the R session.
#'
#' In all other contexts, `gargle_oob_default()` consults the option named
#' `"gargle_oob_default"`, then the option named `"httr_oob_default"`, and
Expand All @@ -68,7 +68,7 @@ gargle_oauth_email <- function() {
#' "oob" stands for out-of-band. Read more about out-of-band authentication in
#' the vignette `vignette("auth-from-web")`.
gargle_oob_default <- function() {
if (is_rstudio_server()) {
if (is_rstudio_server() || is_google_colab()) {
# TODO: Is there a better, more general condition we could use to detect
# whether OOB is necessary?
# Idea from @jcheng: check if it's an SSH session?
Expand All @@ -92,3 +92,25 @@ gargle_oob_default <- function() {
gargle_oauth_cache <- function() {
getOption("gargle_oauth_cache", default = NA)
}

#' @rdname gargle_options
#' @export
#' @section `gargle_oauth_client_type`:
#' `gargle_oauth_client_type()` returns the option named
#' "gargle_oauth_client_type", if defined. If defined, the option must be either
#' "installed" or "web". If the option is not defined, the function returns:
#' * "web" on RStudio Server, Posit Workbench, Posit Cloud, or Google
#' Colaboratory
#' * "installed" otherwise
Comment on lines +99 to +104
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this rigged differently from gargle_oob_default(), where we impose our own logic before consulting an option? Here we first consult the option and impose our own logic if the option is undefined.

Because it seems like we must do some form of OOB auth on RStudio Server, Posit Workbench, Posit Cloud, or Google Colab. Starting a local webserver will never make sense in this context.

Whereas, once we know we're doing some form of OOB, the client type determines whether it's conventional OOB ("installed" client type) or pseudo-OOB ("web" client type). I expect conventional OOB to be relatively rare these days, but it is still possible.

#' Primarily intended to help infer the most suitable OAuth client when a user
#' is relying on a built-in client, such as the tidyverse client used by
#' packages like bigrquery, googledrive, and googlesheets4.
gargle_oauth_client_type <- function() {
opt <- getOption("gargle_oauth_client_type")
if (is.null(opt)) {
if(is_rstudio_server() || is_google_colab()) "web" else "installed"
} else {
check_string(opt)
arg_match(opt, values = c("installed", "web"))
}
}
12 changes: 10 additions & 2 deletions R/oauth-cache.R
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ cache_allowed <- function(path) {
gargle_info("
Is it OK to cache OAuth access credentials in the folder \\
{.path {path}} between R sessions?")
utils::menu(c("Yes", "No")) == 1
if(is_google_colab()) {
is_ok_readline()
} else {
utils::menu(c("Yes", "No")) == 1
}
}

cache_create <- function(path) {
Expand Down Expand Up @@ -321,7 +325,11 @@ token_match <- function(candidate, existing, package = "gargle") {
"Select a pre-authorised account or enter '0' to obtain a new token.",
"Press Esc/Ctrl + C to cancel."
))
choice <- utils::menu(emails)
if(is_google_colab()) {
choice <- choose_readline(emails)
} else {
choice <- utils::menu(emails)
}

if (choice == 0) {
NULL
Expand Down
37 changes: 37 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,43 @@ is_rstudio_server <- function() {
Sys.getenv("RSTUDIO_PROGRAM_MODE") == "server"
}

is_google_colab <- function() {
# idea from https://stackoverflow.com/a/74930276
# 2023-02-21 I created new notebook with
# https://colab.research.google.com/#create=true&language=r
# and I see:
# Sys.getenv("COLAB_RELEASE_TAG") returns 'release-colab-20230216-060056-RC01'
#
# https://github.com/r-lib/gargle/issues/140#issuecomment-1439111627
# via @craigcitro, the existence of this directory is another indicator:
# /var/colab/hostname
nzchar(Sys.getenv("COLAB_RELEASE_TAG"))
}

# readline() has been shimmed in IRkernel, to work around the fact that
# Jupyter sessions are detected as non-interactive
# (note there is no similar shim for utils::menu())
# https://github.com/IRkernel/IRkernel/blob/d0d5ccccee23d798d53b79e14c5ab5935b17f8d8/R/execution.r#L131-L137
is_ok_readline <- function(q = "[y/N]? ") {
ans <- trimws(readline(q))
tolower(ans) %in% c("y", "yes", "yeah", "yep")
}

# emulate utils::menu(), but only using readline
# note this uses gargle_info()
# caller is responsible for verbosity level
choose_readline <- function(choices, prompt = "Selection: ") {
stopifnot(length(choices) > 0)
ints <- seq_along(choices)
gargle_info(c("", paste0(ints, ": ", choices), ""))
ints <- c(0L, ints)
while (TRUE) {
sel <- trimws(readline(prompt))
m <- match(sel, as.character(ints))
if (!is.na(m)) return(ints[[m]])
}
}

add_line <- function(path, line) {
if (file_exists(path)) {
lines <- readLines(path, warn = FALSE)
Expand Down
26 changes: 22 additions & 4 deletions man/gargle_options.Rd

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

10 changes: 9 additions & 1 deletion tests/testthat/test-assets.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ test_that("default options", {
withr::local_options(list(
gargle_oauth_cache = NULL,
gargle_oob_default = NULL, httr_oob_default = NULL,
gargle_oauth_client_type = NULL,
gargle_oauth_email = NULL,
gargle_verbosity = NULL,
gargle_quiet = NULL
))
expect_equal(gargle_oauth_cache(), NA)
if (is_rstudio_server()) {
if (is_rstudio_server() || is_google_colab()) {
expect_true(gargle_oob_default())
expect_equal(gargle_oauth_client_type(), "web")
} else {
expect_false(gargle_oob_default())
expect_equal(gargle_oauth_client_type(), "installed")
}
expect_null(gargle_oauth_email())
expect_equal(gargle_verbosity(), "info")
Expand All @@ -32,6 +35,11 @@ test_that("gargle_oob_default() consults httr's option", {
expect_true(gargle_oob_default())
})

test_that("gargle_oauth_client_type() consults the option", {
withr::local_options(list(gargle_oauth_client_type = "web"))
expect_equal(gargle_oauth_client_type(), "web")
})

test_that("gargle API key", {
key <- gargle_api_key()
expect_true(is_string(key))
Expand Down