From 3829b0c42a2b6220bef2f3608405c911cb1671df Mon Sep 17 00:00:00 2001 From: David Mayer Date: Thu, 22 Sep 2022 13:25:14 -0600 Subject: [PATCH 01/19] use new oauth app - complies with new Google requirements, no OOB --- R/sysdata.rda | Bin 542 -> 550 bytes inst/secret/client_secret.json | Bin 433 -> 416 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/R/sysdata.rda b/R/sysdata.rda index 0adc198578ed32c03192c0a224e602c40626552a..98df0b11c4e8858722e436cf988930e96e2d965d 100644 GIT binary patch literal 550 zcmV+>0@?i^iwFP!000001FcluPunmMcapYg1C=I?y+`bUBCUle(2s~c4UKF_8-g+Q zt;$lTaqGq*i9rSJpWge%oWx|QDWj8S>E!#qi|>9pKOUOePy+xcpdw2^sWKK+FTcER z_W{!|K>`)fV0UFQAr1hlBIs*zE<~#=+Cvzo1wej&2rgBO z0vI#q#UG_~opz%iwRMjMZh}bA+STtgT}Qz+iqSU=TYi#6mPH(z_#-C_TZ{r{4)wSC z_JoiLh9?oB;raF(7WMR*!9^M|Fb=v#*XZ^4dxvd0n@)Yhq&}YZ9M*jA$~0*oK{}wO zH@^xytW?~|i_jQGE(sDCBpsK`pQ#o@_LNCl@Z|Hmi?dE$J@oF!p!;nqpU5mcDDkXzJ+61%$-q_^G&W-|23=VU3hKW_yog?Rd56X zFTipA;u$?yq&xl%weqtU5G)zX4zgu&OYv(-u`1{q1#(yHk;=g=bh=pvwh*E)Bn$5L o|3dYnKVk-Bq1x*;AT1=?EMr(m_i81cOGC=&H}lKtNZAM@J}yWdcyI zABsySMHn)Ec=AWtqGdJKtFaXoILr~jqeJV)v@C?L5JML!93^>9J&y!Mp7=qQjkw@o z1+CZCFeW5U;d@Go>}>dgqjs-va9tu6_Uza`wjFos%*QNqY&V#%=Ci~mupl;bkFI8y zGiu3C*-(WrErNT40FGrs0>q3>>-#(-gR8Rh4*qY^$58bJ{acERBzu+r7gX(ZRLw@cBWdt zYT0aKmCsQnatW~?VNuP`de^!&Kq~=Ht3o#C z_CVSXquqau)~2)3#C_~ez` zSz0&Z8@lqdH}sQ?vV-P%!qWU!N}ejZe#6>(=Ex94Rw~_>Nv%M1&SJ&=!~bIR*59yV gu#)ZBE;cLZeVM*WjORP4EOn=n-;EZ;7S#m+0LvZ^3jhEB diff --git a/inst/secret/client_secret.json b/inst/secret/client_secret.json index 3209f2db0f842746300d9b185a4f34729466efdc..36a28ae926ddace041035ce5ffe183bfca9c8a7b 100644 GIT binary patch delta 136 zcmV;30C)ef1E2#CLRS9ah*R=q&QB~M@TYi9kq{^#(zet>PKpbUHi#;MfCl=On8v94 z!|Je0`72MmxVy}=ky!mp!2r}N2-?W&n_5`+wyZDTTo0SGy>k2C*8qcUh60#$4@Zec qhPV0F>8`Y+F?(8|7U^i1iakHzWR)7Fv@FZ_Zpv8`+I|#RfoZ9hWkzoR delta 153 zcmV;K0A~N71F-`TkIKS)YRx}&%#m^+5uD{Z Date: Thu, 22 Sep 2022 13:26:04 -0600 Subject: [PATCH 02/19] for local authentication flows, set up an oauth listener to receive authorization code, as OOB is now forbidden --- NAMESPACE | 1 + R/get_access_token.R | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index d6d6eb1..050e450 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -20,6 +20,7 @@ importFrom(httr,GET) importFrom(httr,POST) importFrom(httr,add_headers) importFrom(httr,content) +importFrom(httr,oauth_listener) importFrom(jsonlite,fromJSON) importFrom(magrittr,"%>%") importFrom(magrittr,extract2) diff --git a/R/get_access_token.R b/R/get_access_token.R index a46f439..fe886a1 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -30,7 +30,7 @@ print.hidden_fn <- function(x, ...) { #' @export #' @importFrom glue glue #' @importFrom jsonlite fromJSON -#' @importFrom httr content GET add_headers POST +#' @importFrom httr content GET add_headers oauth_listener POST get_access_token <- function(cached_credentials = '~/.googleGroupR_cache.rds') { # OAuth 2.0 Info ## Token URI @@ -86,13 +86,14 @@ get_access_token <- function(cached_credentials = '~/.googleGroupR_cache.rds') { saveRDS(object = cached_token, file = cached_credentials) token$access_token } else{ - message('Not sure how to handle current token situation.') + message('Not sure how to handle current token situation. Go ask an adult.') } } } else { - message(glue::glue('Please visit the following URL to generate an authorization code: {auth_link}')) - auth_code <- readline(prompt = glue::glue('Enter authorization code:')) - access_token_body <- list(code=auth_code, + # message(glue::glue('Please visit the following URL to generate an authorization code: {auth_link}')) + # auth_code <- readline(prompt = glue::glue('Enter authorization code:')) + auth_code <- oauth_listener(auth_link, is_interactive = interactive()) + access_token_body <- list(code=auth_code$code, client_id=installed_app()$key, client_secret=installed_app()$secret, redirect_uri=installed_app()$redirect_uri, From c5fb3ccfa2ff50cf57fc90ee11feee79df273f72 Mon Sep 17 00:00:00 2001 From: David Mayer Date: Thu, 22 Sep 2022 13:41:58 -0600 Subject: [PATCH 03/19] additional fix for local auth when cached token is invalid --- R/get_access_token.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/R/get_access_token.R b/R/get_access_token.R index fe886a1..706481c 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -43,9 +43,10 @@ get_access_token <- function(cached_credentials = '~/.googleGroupR_cache.rds') { if(file.exists(cached_credentials) ) { cached_token <- readRDS(cached_credentials) if ('error' %in% names(cached_token) ) { - message(glue::glue('Please visit the following URL to generate an authorization code: {auth_link}')) - auth_code <- readline(prompt = glue::glue('Enter authorization code:')) - access_token_body <- list(code=auth_code, + # message(glue::glue('Please visit the following URL to generate an authorization code: {auth_link}')) + # auth_code <- readline(prompt = glue::glue('Enter authorization code:')) + auth_code <- oauth_listener(auth_link, is_interactive = interactive()) + access_token_body <- list(code=auth_code$code, client_id=installed_app()$key, client_secret=installed_app()$secret, redirect_uri=installed_app()$redirect_uri, From 288a12048822ad6ae832891f74e6aebc6ca79c95 Mon Sep 17 00:00:00 2001 From: David Mayer Date: Thu, 22 Sep 2022 14:03:29 -0600 Subject: [PATCH 04/19] auth flow if/else formatting - improve readability --- R/get_access_token.R | 94 ++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/R/get_access_token.R b/R/get_access_token.R index 706481c..50fe804 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -54,53 +54,53 @@ get_access_token <- function(cached_credentials = '~/.googleGroupR_cache.rds') { token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) saveRDS(object = token, file = cached_credentials) token$access_token - } else { - google_oauth_url <- 'https://accounts.google.com/o/oauth2/' - token_info <- httr::content(httr::GET(glue::glue('{google_oauth_url}tokeninfo?access_token={cached_token$access_token}'))) - if('expires_in' %in% names(token_info) ){ - if(token_info$expires_in > 0 ) { - message('Using access token from cache.') - cached_token$access_token - } else { - message('Cached token expired, obtaining new access token.') - # Create Refresh cURL command pieces - refresh_header <- httr::add_headers('Content-Type' = 'application/x-www-form-urlencoded') - refresh_body <- list(grant_type='refresh_token', - refresh_token=cached_token$refresh_token, - client_id=installed_app()$key, - client_secret=installed_app()$secret) - token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) - cached_token$access_token <- token$access_token - saveRDS(object = cached_token, file = cached_credentials) - token$access_token + } else { + google_oauth_url <- 'https://accounts.google.com/o/oauth2/' + token_info <- httr::content(httr::GET(glue::glue('{google_oauth_url}tokeninfo?access_token={cached_token$access_token}'))) + if('expires_in' %in% names(token_info) ){ + if(token_info$expires_in > 0 ) { + message('Using access token from cache.') + cached_token$access_token + } else { + message('Cached token expired, obtaining new access token.') + # Create Refresh cURL command pieces + refresh_header <- httr::add_headers('Content-Type' = 'application/x-www-form-urlencoded') + refresh_body <- list(grant_type='refresh_token', + refresh_token=cached_token$refresh_token, + client_id=installed_app()$key, + client_secret=installed_app()$secret) + token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) + cached_token$access_token <- token$access_token + saveRDS(object = cached_token, file = cached_credentials) + token$access_token + } + } else if ('error' %in% names(token_info) ){ + message('Cached token expired, obtaining new access token.') + # Create Refresh cURL command pieces + refresh_header <- httr::add_headers('Content-Type' = 'application/x-www-form-urlencoded') + refresh_body <- list(grant_type='refresh_token', + refresh_token=cached_token$refresh_token, + client_id=installed_app()$key, + client_secret=installed_app()$secret) + token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) + cached_token$access_token <- token$access_token + saveRDS(object = cached_token, file = cached_credentials) + token$access_token + } else { + message('Not sure how to handle current token situation. Go ask an adult.') + } } - } else if ('error' %in% names(token_info) ){ - message('Cached token expired, obtaining new access token.') - # Create Refresh cURL command pieces - refresh_header <- httr::add_headers('Content-Type' = 'application/x-www-form-urlencoded') - refresh_body <- list(grant_type='refresh_token', - refresh_token=cached_token$refresh_token, - client_id=installed_app()$key, - client_secret=installed_app()$secret) - token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) - cached_token$access_token <- token$access_token - saveRDS(object = cached_token, file = cached_credentials) - token$access_token - } else{ - message('Not sure how to handle current token situation. Go ask an adult.') + } else { + # message(glue::glue('Please visit the following URL to generate an authorization code: {auth_link}')) + # auth_code <- readline(prompt = glue::glue('Enter authorization code:')) + auth_code <- oauth_listener(auth_link, is_interactive = interactive()) + access_token_body <- list(code=auth_code$code, + client_id=installed_app()$key, + client_secret=installed_app()$secret, + redirect_uri=installed_app()$redirect_uri, + grant_type='authorization_code') + token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) + saveRDS(object = token, file = cached_credentials) + token$access_token } } - } else { - # message(glue::glue('Please visit the following URL to generate an authorization code: {auth_link}')) - # auth_code <- readline(prompt = glue::glue('Enter authorization code:')) - auth_code <- oauth_listener(auth_link, is_interactive = interactive()) - access_token_body <- list(code=auth_code$code, - client_id=installed_app()$key, - client_secret=installed_app()$secret, - redirect_uri=installed_app()$redirect_uri, - grant_type='authorization_code') - token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) - saveRDS(object = token, file = cached_credentials) - token$access_token - } -} From 45254a344a36d037a931bb4c04a1094e1fafba7f Mon Sep 17 00:00:00 2001 From: David Mayer Date: Thu, 22 Sep 2022 14:18:29 -0600 Subject: [PATCH 05/19] change default location for caching credentials --- R/get_access_token.R | 7 ++++++- man/get_access_token.Rd | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/R/get_access_token.R b/R/get_access_token.R index 50fe804..e651960 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -31,7 +31,12 @@ print.hidden_fn <- function(x, ...) { #' @importFrom glue glue #' @importFrom jsonlite fromJSON #' @importFrom httr content GET add_headers oauth_listener POST -get_access_token <- function(cached_credentials = '~/.googleGroupR_cache.rds') { +get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googleGroupR_cache.rds') { + # Cache Directory Setup + cache_dir <- dirname(cached_credentials) + if(dir.exists(cache_dir) == FALSE) { + dir.create(cache_dir) + } # OAuth 2.0 Info ## Token URI token_uri <- "https://oauth2.googleapis.com/token" diff --git a/man/get_access_token.Rd b/man/get_access_token.Rd index a2a77bc..781272d 100644 --- a/man/get_access_token.Rd +++ b/man/get_access_token.Rd @@ -4,7 +4,9 @@ \alias{get_access_token} \title{Get Access Token} \usage{ -get_access_token(cached_credentials = "~/.googleGroupR_cache.rds") +get_access_token( + cached_credentials = "~/.config/googleGroupR/.googleGroupR_cache.rds" +) } \arguments{ \item{cached_credentials}{Location to read from/store credentials.} From 882244b435b202645d42fa4761ef857ed90f82b2 Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 09:19:53 -0600 Subject: [PATCH 06/19] add additional flow for when access_token is no longer available --- R/get_access_token.R | 60 ++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/R/get_access_token.R b/R/get_access_token.R index e651960..d1a54e7 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -59,14 +59,37 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) saveRDS(object = token, file = cached_credentials) token$access_token - } else { - google_oauth_url <- 'https://accounts.google.com/o/oauth2/' - token_info <- httr::content(httr::GET(glue::glue('{google_oauth_url}tokeninfo?access_token={cached_token$access_token}'))) - if('expires_in' %in% names(token_info) ){ - if(token_info$expires_in > 0 ) { - message('Using access token from cache.') - cached_token$access_token - } else { + } else if (!'access_token' %in% names(cached_token)) { + auth_code <- oauth_listener(auth_link, is_interactive = interactive()) + access_token_body <- list(code=auth_code$code, + client_id=installed_app()$key, + client_secret=installed_app()$secret, + redirect_uri=installed_app()$redirect_uri, + grant_type='authorization_code') + token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) + saveRDS(object = token, file = cached_credentials) + token$access_token + } else { + google_oauth_url <- 'https://accounts.google.com/o/oauth2/' + token_info <- httr::content(httr::GET(glue::glue('{google_oauth_url}tokeninfo?access_token={cached_token$access_token}'))) + if('expires_in' %in% names(token_info) ){ + if(token_info$expires_in > 0 ) { + message('Using access token from cache.') + cached_token$access_token + } else { + message('Cached token expired, obtaining new access token.') + # Create Refresh cURL command pieces + refresh_header <- httr::add_headers('Content-Type' = 'application/x-www-form-urlencoded') + refresh_body <- list(grant_type='refresh_token', + refresh_token=cached_token$refresh_token, + client_id=installed_app()$key, + client_secret=installed_app()$secret) + token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) + cached_token$access_token <- token$access_token + saveRDS(object = cached_token, file = cached_credentials) + token$access_token + } + } else if ('error' %in% names(token_info) ){ message('Cached token expired, obtaining new access token.') # Create Refresh cURL command pieces refresh_header <- httr::add_headers('Content-Type' = 'application/x-www-form-urlencoded') @@ -78,23 +101,10 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl cached_token$access_token <- token$access_token saveRDS(object = cached_token, file = cached_credentials) token$access_token - } - } else if ('error' %in% names(token_info) ){ - message('Cached token expired, obtaining new access token.') - # Create Refresh cURL command pieces - refresh_header <- httr::add_headers('Content-Type' = 'application/x-www-form-urlencoded') - refresh_body <- list(grant_type='refresh_token', - refresh_token=cached_token$refresh_token, - client_id=installed_app()$key, - client_secret=installed_app()$secret) - token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) - cached_token$access_token <- token$access_token - saveRDS(object = cached_token, file = cached_credentials) - token$access_token - } else { - message('Not sure how to handle current token situation. Go ask an adult.') - } - } + } else { + message('Not sure how to handle current token situation. Go ask an adult.') + } + } } else { # message(glue::glue('Please visit the following URL to generate an authorization code: {auth_link}')) # auth_code <- readline(prompt = glue::glue('Enter authorization code:')) From 41d6e3999be7fdf0e5c597aaf44f4b55907253a4 Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 09:51:13 -0600 Subject: [PATCH 07/19] Add messaging for RStudio Server installs and provide info on how to work around lack of OOB for Google Auth. --- NAMESPACE | 2 ++ R/get_access_token.R | 28 +++++++++++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 050e450..b4ba047 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -26,4 +26,6 @@ importFrom(magrittr,"%>%") importFrom(magrittr,extract2) importFrom(purrr,map_chr) importFrom(rlang,.data) +importFrom(rlang,format_error_bullets) +importFrom(rlang,inform) importFrom(tibble,enframe) diff --git a/R/get_access_token.R b/R/get_access_token.R index d1a54e7..d1803ca 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -31,6 +31,7 @@ print.hidden_fn <- function(x, ...) { #' @importFrom glue glue #' @importFrom jsonlite fromJSON #' @importFrom httr content GET add_headers oauth_listener POST +#' @importFrom rlang format_error_bullets inform get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googleGroupR_cache.rds') { # Cache Directory Setup cache_dir <- dirname(cached_credentials) @@ -106,16 +107,21 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl } } } else { - # message(glue::glue('Please visit the following URL to generate an authorization code: {auth_link}')) - # auth_code <- readline(prompt = glue::glue('Enter authorization code:')) - auth_code <- oauth_listener(auth_link, is_interactive = interactive()) - access_token_body <- list(code=auth_code$code, - client_id=installed_app()$key, - client_secret=installed_app()$secret, - redirect_uri=installed_app()$redirect_uri, - grant_type='authorization_code') - token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) - saveRDS(object = token, file = cached_credentials) - token$access_token + if(Sys.getenv('RSTUDIO_HTTP_REFERER' == '')) { + # message(glue::glue('Please visit the following URL to generate an authorization code: {auth_link}')) + + auth_code <- oauth_listener(auth_link, is_interactive = interactive()) + access_token_body <- list(code=auth_code$code, + client_id=installed_app()$key, + client_secret=installed_app()$secret, + redirect_uri=installed_app()$redirect_uri, + grant_type='authorization_code') + token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) + saveRDS(object = token, file = cached_credentials) + token$access_token + } else { + rlang::inform(rlang::format_error_bullets(c("x" = glue::glue("I'm sorry, Google no longer allows Out Of Band (OOB) OAuth 2.0 authentication flows for server environments. \n * Please generate credentials using this package locally and upload to an appropriate location, {cache_dir} by default."), + "i" = 'https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html'))) + } } } From 93e7f84d8d10ed83ccb8d89e0e5a264e15912c7a Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 10:10:29 -0600 Subject: [PATCH 08/19] messaging overhaul - additional processes messages - don't print access token to console - use rlang flavor of messaging (inform) --- R/get_access_token.R | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/R/get_access_token.R b/R/get_access_token.R index d1803ca..41b4491 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -36,6 +36,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl # Cache Directory Setup cache_dir <- dirname(cached_credentials) if(dir.exists(cache_dir) == FALSE) { + rlang::inform(rlang::format_error_bullets(c("i" = glue::glue('Creating googleGroupR credential cache at {cache_dir}')))) dir.create(cache_dir) } # OAuth 2.0 Info @@ -49,8 +50,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl if(file.exists(cached_credentials) ) { cached_token <- readRDS(cached_credentials) if ('error' %in% names(cached_token) ) { - # message(glue::glue('Please visit the following URL to generate an authorization code: {auth_link}')) - # auth_code <- readline(prompt = glue::glue('Enter authorization code:')) + rlang::inform(rlang::format_error_bullets(c("x" = 'Error encountered with cached credentials. Obtaining new credentials.'))) auth_code <- oauth_listener(auth_link, is_interactive = interactive()) access_token_body <- list(code=auth_code$code, client_id=installed_app()$key, @@ -59,8 +59,8 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl grant_type='authorization_code') token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) saveRDS(object = token, file = cached_credentials) - token$access_token } else if (!'access_token' %in% names(cached_token)) { + rlang::inform(rlang::format_error_bullets(c("x" = 'Error encountered with cached credentials. Obtaining new credentials.'))) auth_code <- oauth_listener(auth_link, is_interactive = interactive()) access_token_body <- list(code=auth_code$code, client_id=installed_app()$key, @@ -69,16 +69,15 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl grant_type='authorization_code') token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) saveRDS(object = token, file = cached_credentials) - token$access_token } else { google_oauth_url <- 'https://accounts.google.com/o/oauth2/' token_info <- httr::content(httr::GET(glue::glue('{google_oauth_url}tokeninfo?access_token={cached_token$access_token}'))) if('expires_in' %in% names(token_info) ){ if(token_info$expires_in > 0 ) { - message('Using access token from cache.') + rlang::inform(rlang::format_error_bullets(c("i" = 'Using access token from cache.'))) cached_token$access_token } else { - message('Cached token expired, obtaining new access token.') + rlang::inform(rlang::format_error_bullets(c("i" = 'Cached token expired, attempting to refresh.'))) # Create Refresh cURL command pieces refresh_header <- httr::add_headers('Content-Type' = 'application/x-www-form-urlencoded') refresh_body <- list(grant_type='refresh_token', @@ -88,10 +87,9 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) cached_token$access_token <- token$access_token saveRDS(object = cached_token, file = cached_credentials) - token$access_token } } else if ('error' %in% names(token_info) ){ - message('Cached token expired, obtaining new access token.') + rlang::inform(rlang::format_error_bullets(c("i" = 'Cached token expired, obtaining new access token.'))) # Create Refresh cURL command pieces refresh_header <- httr::add_headers('Content-Type' = 'application/x-www-form-urlencoded') refresh_body <- list(grant_type='refresh_token', @@ -101,15 +99,12 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) cached_token$access_token <- token$access_token saveRDS(object = cached_token, file = cached_credentials) - token$access_token } else { message('Not sure how to handle current token situation. Go ask an adult.') } } } else { if(Sys.getenv('RSTUDIO_HTTP_REFERER' == '')) { - # message(glue::glue('Please visit the following URL to generate an authorization code: {auth_link}')) - auth_code <- oauth_listener(auth_link, is_interactive = interactive()) access_token_body <- list(code=auth_code$code, client_id=installed_app()$key, @@ -118,7 +113,6 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl grant_type='authorization_code') token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) saveRDS(object = token, file = cached_credentials) - token$access_token } else { rlang::inform(rlang::format_error_bullets(c("x" = glue::glue("I'm sorry, Google no longer allows Out Of Band (OOB) OAuth 2.0 authentication flows for server environments. \n * Please generate credentials using this package locally and upload to an appropriate location, {cache_dir} by default."), "i" = 'https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html'))) From e42190a7810f04e6787abe84422886ad16d48b8a Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 10:13:39 -0600 Subject: [PATCH 09/19] additional messaging upgrade --- R/list_group_members.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/R/list_group_members.R b/R/list_group_members.R index ac1e835..644161e 100644 --- a/R/list_group_members.R +++ b/R/list_group_members.R @@ -9,11 +9,12 @@ #' @export #' @importFrom glue glue #' @importFrom httr add_headers content GET +#' @importFrom rlang format_error_bullets inform list_group_members <- function (domain, group) { if (missing(domain) ) { - message('Please specify a GSuite domain.') + rlang::inform(rlang::format_error_bullets(c("x"='Please specify a GSuite domain.'))) } else if (missing(group)) { - message('Please specify a group name.') + rlang::inform(rlang::format_error_bullets(c("x"='Please specify a group name.'))) }else { group_id <- get_group_id(domain, group) access_token <- get_access_token() From 0242a10f09bb715b6712211a1438fbefcc649d4b Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 10:15:54 -0600 Subject: [PATCH 10/19] another messaging update --- R/list_groups.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/list_groups.R b/R/list_groups.R index 681e56c..4ee8132 100644 --- a/R/list_groups.R +++ b/R/list_groups.R @@ -8,9 +8,10 @@ #' @export #' @importFrom glue glue #' @importFrom httr add_headers content GET +#' @importFrom rlang format_error_bullets inform list_groups <- function(domain) { if (missing(domain) ) { - message('Please specify a GSuite domain.') + rlang::inform(rlang::format_error_bullets(c("x" = 'Please specify a GSuite domain.'))) } else { access_token <- get_access_token() auth_header <- httr::add_headers('Authorization' = glue::glue('Bearer {access_token}')) From 5c6b2e0cd0dc86d36ec9be9702d8bf557c9e42b7 Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 10:19:07 -0600 Subject: [PATCH 11/19] fix RStudio Server conditional --- R/get_access_token.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get_access_token.R b/R/get_access_token.R index 41b4491..d26b606 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -104,7 +104,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl } } } else { - if(Sys.getenv('RSTUDIO_HTTP_REFERER' == '')) { + if(Sys.getenv('RSTUDIO_HTTP_REFERER') == '') { auth_code <- oauth_listener(auth_link, is_interactive = interactive()) access_token_body <- list(code=auth_code$code, client_id=installed_app()$key, From 0d1fc56b65a969e9d6db724f8805c35a4f856898 Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 10:24:32 -0600 Subject: [PATCH 12/19] Use different conditional - HTTP referer doesn't always detect RStudio Server installation --- R/get_access_token.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get_access_token.R b/R/get_access_token.R index d26b606..eae7ce7 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -104,7 +104,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl } } } else { - if(Sys.getenv('RSTUDIO_HTTP_REFERER') == '') { + if(Sys.getenv('RSTUDIO_PROGRAM_MODE') == 'desktop') { auth_code <- oauth_listener(auth_link, is_interactive = interactive()) access_token_body <- list(code=auth_code$code, client_id=installed_app()$key, From 2bd6fda439bbb08e7b3671fe9fbf2d89b3533864 Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 10:33:12 -0600 Subject: [PATCH 13/19] don't allow unauthenticated requests - this particular command will return a result as it is a public facing Google API. For consistency with other package functions, make sure a user first authenticates. --- R/list_groups.R | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/R/list_groups.R b/R/list_groups.R index 4ee8132..313dd4a 100644 --- a/R/list_groups.R +++ b/R/list_groups.R @@ -12,11 +12,15 @@ list_groups <- function(domain) { if (missing(domain) ) { rlang::inform(rlang::format_error_bullets(c("x" = 'Please specify a GSuite domain.'))) - } else { - access_token <- get_access_token() - auth_header <- httr::add_headers('Authorization' = glue::glue('Bearer {access_token}')) - httr::content(httr::GET(glue::glue('https://www.googleapis.com/admin/directory/v1/groups/?domain={domain}'), - auth_header) - ) - } + } else { + access_token <- get_access_token() + if(is.null(access_token)){ + rlang::inform(rlang::format_error_bullets("x" = 'Please provide authentication credentials.')) + } else { + auth_header <- httr::add_headers('Authorization' = glue::glue('Bearer {access_token}')) + httr::content(httr::GET(glue::glue('https://www.googleapis.com/admin/directory/v1/groups/?domain={domain}'), + auth_header) + ) + } + } } From 22fc43ececf5752ddb38221aa4c89f7b3ac93258 Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 10:37:44 -0600 Subject: [PATCH 14/19] allow adequate time for token generation/refresh --- R/list_groups.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/list_groups.R b/R/list_groups.R index 313dd4a..8548509 100644 --- a/R/list_groups.R +++ b/R/list_groups.R @@ -14,6 +14,7 @@ list_groups <- function(domain) { rlang::inform(rlang::format_error_bullets(c("x" = 'Please specify a GSuite domain.'))) } else { access_token <- get_access_token() + Sys.sleep(5) if(is.null(access_token)){ rlang::inform(rlang::format_error_bullets("x" = 'Please provide authentication credentials.')) } else { From ec15d6e8e7d8ecb3ccd875da75726a3c2eaaa6c1 Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 10:39:46 -0600 Subject: [PATCH 15/19] ammend previous - globally allow additional time for API calls using get_access_token() --- R/get_access_token.R | 5 +++++ R/list_groups.R | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/R/get_access_token.R b/R/get_access_token.R index eae7ce7..6e564d8 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -59,6 +59,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl grant_type='authorization_code') token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) saveRDS(object = token, file = cached_credentials) + Sys.sleep(5) } else if (!'access_token' %in% names(cached_token)) { rlang::inform(rlang::format_error_bullets(c("x" = 'Error encountered with cached credentials. Obtaining new credentials.'))) auth_code <- oauth_listener(auth_link, is_interactive = interactive()) @@ -69,6 +70,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl grant_type='authorization_code') token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) saveRDS(object = token, file = cached_credentials) + Sys.sleep(5) } else { google_oauth_url <- 'https://accounts.google.com/o/oauth2/' token_info <- httr::content(httr::GET(glue::glue('{google_oauth_url}tokeninfo?access_token={cached_token$access_token}'))) @@ -87,6 +89,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) cached_token$access_token <- token$access_token saveRDS(object = cached_token, file = cached_credentials) + Sys.sleep(5) } } else if ('error' %in% names(token_info) ){ rlang::inform(rlang::format_error_bullets(c("i" = 'Cached token expired, obtaining new access token.'))) @@ -99,6 +102,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) cached_token$access_token <- token$access_token saveRDS(object = cached_token, file = cached_credentials) + Sys.sleep(5) } else { message('Not sure how to handle current token situation. Go ask an adult.') } @@ -113,6 +117,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl grant_type='authorization_code') token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) saveRDS(object = token, file = cached_credentials) + Sys.sleep(5) } else { rlang::inform(rlang::format_error_bullets(c("x" = glue::glue("I'm sorry, Google no longer allows Out Of Band (OOB) OAuth 2.0 authentication flows for server environments. \n * Please generate credentials using this package locally and upload to an appropriate location, {cache_dir} by default."), "i" = 'https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html'))) diff --git a/R/list_groups.R b/R/list_groups.R index 8548509..313dd4a 100644 --- a/R/list_groups.R +++ b/R/list_groups.R @@ -14,7 +14,6 @@ list_groups <- function(domain) { rlang::inform(rlang::format_error_bullets(c("x" = 'Please specify a GSuite domain.'))) } else { access_token <- get_access_token() - Sys.sleep(5) if(is.null(access_token)){ rlang::inform(rlang::format_error_bullets("x" = 'Please provide authentication credentials.')) } else { From dca59d2806c31c5a1e67ff682862078d990ed61a Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 10:56:05 -0600 Subject: [PATCH 16/19] correction - add back token return values BUT - prevent from printing to console --- R/get_access_token.R | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/R/get_access_token.R b/R/get_access_token.R index 6e564d8..85dcd02 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -59,7 +59,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl grant_type='authorization_code') token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) saveRDS(object = token, file = cached_credentials) - Sys.sleep(5) + invisible(token$access_token) } else if (!'access_token' %in% names(cached_token)) { rlang::inform(rlang::format_error_bullets(c("x" = 'Error encountered with cached credentials. Obtaining new credentials.'))) auth_code <- oauth_listener(auth_link, is_interactive = interactive()) @@ -70,7 +70,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl grant_type='authorization_code') token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) saveRDS(object = token, file = cached_credentials) - Sys.sleep(5) + invisible(token$access_token) } else { google_oauth_url <- 'https://accounts.google.com/o/oauth2/' token_info <- httr::content(httr::GET(glue::glue('{google_oauth_url}tokeninfo?access_token={cached_token$access_token}'))) @@ -89,7 +89,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) cached_token$access_token <- token$access_token saveRDS(object = cached_token, file = cached_credentials) - Sys.sleep(5) + invisible(token$access_token) } } else if ('error' %in% names(token_info) ){ rlang::inform(rlang::format_error_bullets(c("i" = 'Cached token expired, obtaining new access token.'))) @@ -102,7 +102,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl token <- httr::content(httr::POST(url = token_uri, refresh_header, body = refresh_body, encode='form')) cached_token$access_token <- token$access_token saveRDS(object = cached_token, file = cached_credentials) - Sys.sleep(5) + invisible(token$access_token) } else { message('Not sure how to handle current token situation. Go ask an adult.') } @@ -117,7 +117,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl grant_type='authorization_code') token <- httr::content(httr::POST(url = token_uri, body = access_token_body)) saveRDS(object = token, file = cached_credentials) - Sys.sleep(5) + invisible(token$access_token) } else { rlang::inform(rlang::format_error_bullets(c("x" = glue::glue("I'm sorry, Google no longer allows Out Of Band (OOB) OAuth 2.0 authentication flows for server environments. \n * Please generate credentials using this package locally and upload to an appropriate location, {cache_dir} by default."), "i" = 'https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html'))) From acfe4cd74efeb00bde93c97af3c4cf979d723449 Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 11:02:42 -0600 Subject: [PATCH 17/19] fix list groups messaging --- R/list_groups.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/list_groups.R b/R/list_groups.R index 313dd4a..69169dd 100644 --- a/R/list_groups.R +++ b/R/list_groups.R @@ -15,7 +15,7 @@ list_groups <- function(domain) { } else { access_token <- get_access_token() if(is.null(access_token)){ - rlang::inform(rlang::format_error_bullets("x" = 'Please provide authentication credentials.')) + rlang::inform(rlang::format_error_bullets(c("x" = 'Error: Unauthenticated'))) } else { auth_header <- httr::add_headers('Authorization' = glue::glue('Bearer {access_token}')) httr::content(httr::GET(glue::glue('https://www.googleapis.com/admin/directory/v1/groups/?domain={domain}'), From e6b19dab98c0b739bc906f543f122848a3ae52ba Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 11:04:06 -0600 Subject: [PATCH 18/19] modify message text --- R/get_access_token.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get_access_token.R b/R/get_access_token.R index 85dcd02..d38a4bc 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -119,7 +119,7 @@ get_access_token <- function(cached_credentials = '~/.config/googleGroupR/.googl saveRDS(object = token, file = cached_credentials) invisible(token$access_token) } else { - rlang::inform(rlang::format_error_bullets(c("x" = glue::glue("I'm sorry, Google no longer allows Out Of Band (OOB) OAuth 2.0 authentication flows for server environments. \n * Please generate credentials using this package locally and upload to an appropriate location, {cache_dir} by default."), + rlang::inform(rlang::format_error_bullets(c("x" = glue::glue("I'm sorry, Google no longer allows Out Of Band (OOB) OAuth 2.0 authentication flows for server environments. \n - Please generate credentials using this package locally and upload to an appropriate location, {cache_dir} by default."), "i" = 'https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html'))) } } From 1519582589c045c2b8d521879c14ecf12e22b9b8 Mon Sep 17 00:00:00 2001 From: David Mayer Date: Fri, 23 Sep 2022 11:11:27 -0600 Subject: [PATCH 19/19] bump version --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 50300d7..3bc17fe 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: googleGroupR Title: An R Wrapper for Google Groups Management using the AdminSDK -Version: 0.0.1.9001 +Version: 0.0.2.9000 Authors@R: c( person(given = "David", family = "Mayer", role = c("aut", "cre"), email = "david.mayer@cuanschutz.edu"), person(given = "The Wiley Lab", role = c("cph", "fnd")) @@ -12,7 +12,7 @@ BugReports: https://github.com/thewileylab/googleGroupR/issues Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.1 +RoxygenNote: 7.2.1 Depends: R (>= 3.5.0) Imports: