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: diff --git a/NAMESPACE b/NAMESPACE index d6d6eb1..b4ba047 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -20,9 +20,12 @@ 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) 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 a46f439..d38a4bc 100644 --- a/R/get_access_token.R +++ b/R/get_access_token.R @@ -30,8 +30,15 @@ print.hidden_fn <- function(x, ...) { #' @export #' @importFrom glue glue #' @importFrom jsonlite fromJSON -#' @importFrom httr content GET add_headers POST -get_access_token <- function(cached_credentials = '~/.googleGroupR_cache.rds') { +#' @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) + 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 ## Token URI token_uri <- "https://oauth2.googleapis.com/token" @@ -43,62 +50,77 @@ 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, + 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, 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 + 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()) + 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) + 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}'))) + if('expires_in' %in% names(token_info) ){ + if(token_info$expires_in > 0 ) { + rlang::inform(rlang::format_error_bullets(c("i" = 'Using access token from cache.'))) + cached_token$access_token + } else { + 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', + 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) + 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.'))) + # 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) + invisible(token$access_token) + } else { + message('Not sure how to handle current token situation. Go ask an adult.') + } + } } 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 + 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, + 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) + invisible(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.') + 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'))) + } } } - } 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, - 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 - } -} 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() diff --git a/R/list_groups.R b/R/list_groups.R index 681e56c..69169dd 100644 --- a/R/list_groups.R +++ b/R/list_groups.R @@ -8,14 +8,19 @@ #' @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.') - } 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) - ) - } + rlang::inform(rlang::format_error_bullets(c("x" = 'Please specify a GSuite domain.'))) + } else { + access_token <- get_access_token() + if(is.null(access_token)){ + 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}'), + auth_header) + ) + } + } } diff --git a/R/sysdata.rda b/R/sysdata.rda index 0adc198..98df0b1 100644 Binary files a/R/sysdata.rda and b/R/sysdata.rda differ diff --git a/inst/secret/client_secret.json b/inst/secret/client_secret.json index 3209f2d..36a28ae 100644 Binary files a/inst/secret/client_secret.json and b/inst/secret/client_secret.json differ 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.}