Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supplying authorization to get slide notes for ottrpal #118

Merged
merged 12 commits into from
Aug 14, 2023
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ vignettes/.httr-oauth$
^vignettes/toc_close.css$
^package_bundles/leanbuild_0.1.2.tar.gz*
^package_bundles/leanbuild_0.1.2.tgz*
inst/extdata/tmp/default*
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ tests/testthat/googlesheets_token.rds
run_test.R
/doc/
/Meta/
.secrets
inst/extdata/tmp/default*
6 changes: 6 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by roxygen2: do not edit by hand

export("%>%")
export(auth_from_secret)
export(authorize)
export(bad_quiz_path)
export(bookdown_destination)
Expand Down Expand Up @@ -60,10 +61,15 @@ importFrom(httr,GET)
importFrom(httr,accept_json)
importFrom(httr,config)
importFrom(httr,content)
importFrom(httr,oauth2.0_token)
importFrom(httr,oauth_app)
importFrom(httr,oauth_endpoints)
importFrom(jsonlite,fromJSON)
importFrom(magrittr,"%>%")
importFrom(readr,write_tsv)
importFrom(utils,download.file)
importFrom(utils,installed.packages)
importFrom(utils,menu)
importFrom(utils,unzip)
importFrom(xml2,read_xml)
importFrom(xml2,xml_find_all)
Expand Down
153 changes: 100 additions & 53 deletions R/auth.R
Original file line number Diff line number Diff line change
@@ -1,74 +1,121 @@
# Make an empty environment where the token will be stored
.tokenEnv <- new.env(parent = emptyenv())

# For now the Token is gonna be NULL because we don't have it yet.
.tokenEnv$Token <- NULL

# A function to set token to environment
# Set token to environment
set_token <- function(value) {
.tokenEnv$Token <- value
return(value)
}

# A function to retrieve token from environment
# Get token from environment
get_token <- function() {
.tokenEnv$Token
}

### Declare all the scopes
scopes_list <- c(
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/presentations",
"https://www.googleapis.com/auth/presentations.readonly"
)

#' Authorize R package to access Google Slides API
#'
#' By providing a Google Cloud Client ID and Client Secret, you obtain an access
#' token from Google's OAuth 2.0 server. This access token is used to access the
#' Google Slides API. If you supply a token, this function will save it for
#' future use. For instructions on creating a Client ID and Client Secret, see
#' \url{https://www.hairizuan.com/rgoogleslides-using-your-own-account-client-id-and-secret/}.
#'
#' If this is your first time running authorize(), it will ask you if you want
#' to use a local file ('.httr-oauth') to cache the access token. If you say
#' "Yes", you will not have to run this function in future R sessions. Make sure
#' to provide ottrpal complete access to your Google Drive files and Google
#' Slides presentations.
#'
#' @param client_id Google Cloud Client ID
#' @param client_secret Google Cloud Client secret
#' @param token OAuth 2.0 Access Token
#' @param ... Additional arguments to [httr::oauth2.0_token()]
#'
#' @return A Token2.0 reference class (RC) object.

#' Authorize R package to access the Google Slides API
#' @description This is a function to authorize the R package to access the Google Slides API interactively.
#' @param token An output from \code{\link{oauth2.0_token}} to set as the authentication token.
#' @param cache Should the token be cached as an .httr-oauth file?
#' @param ... Additional arguments to send to \code{\link{oauth2.0_token}}
#' @return OAuth token saved to the environment so the package can use the users' Google data
#' @importFrom utils menu installed.packages
#' @importFrom httr oauth_app oauth_endpoints oauth2.0_token
#' @export
#' @examples \dontrun{
#'
#' @examples
#' \dontrun{
#' # Generate token from Client ID and Client Secret
#' authorize(client_id = "MY_CLIENT_ID", client_secret = "MY_CLIENT_SECRET")
#'
#' # Provides user-generated token
#' authorize(token = my_token)
#' authorize()
#' }
authorize <- function(client_id = NULL,
client_secret = NULL,
token = NULL,
...) {
# client id or secret not provided
if ((is.null(client_id) | is.null(client_secret)) & is.null(token)) {
stop("Please generate a client secret and client key following these instructions:\n",
"https://www.hairizuan.com/rgoogleslides-using-your-own-account-client-id-and-secret/")
authorize <- function(token = NULL, cache = FALSE, ...) {
if (!cache) {
cache_it <- menu(c("Yes store credentials as .httr-oauth file", "No do not store credentials, I will re-run this authorize() in my next R session"))
if (cache_it == 1) {
message("You chose to cache your credentials, if you change your mind, just delete the .httr-oauth. Be careful not to push this file to GitHub or share it anywhere.")
}
} else {
cache_it <- 1
}
if (is.null(token)) {
# setup app
app <- httr::oauth_app(appname = "googleslides",
key = client_id,
secret = client_secret)
# google endpoints
endpoint <- httr::oauth_endpoints("google")
# generate token
token <- httr::oauth2.0_token(endpoint = endpoint,
app = app,
scope = c("https://www.googleapis.com/auth/presentations",
"https://www.googleapis.com/auth/drive.readonly"),
...)
token <- httr::oauth2.0_token(
endpoint = app_set_up()$endpoint,
app = app_set_up()$app,
cache = cache_it == 1,
scope = scopes_list,
...
)
}
set_token(token)
invisible(token)
return(invisible(token))
}

#' Use secrets to authorize R package to access Google Slides API
#' @description This is a function to authorize the R package to access the Google Slides API. If no
#' client.id and client.secret is provided, the package would provide predefined values.
#' @param access_token Access token can be obtained from running authorize() interactively: token <-authorize(); token$credentials$access_token
#' @param refresh_token Refresh token can be obtained from running authorize() interactively: token <-authorize(); token$credentials$refresh_token
#' @return OAuth token saved to the environment so the package can use the users' Google data
#' @importFrom utils menu installed.packages
#' @importFrom httr oauth_app oauth_endpoints oauth2.0_token
#' @export
#' @examples \dontrun{
#'
#' token <- authorize()
#'
#' auth_from_secret(
#' token$credentials$access_token,
#' token$credentials$refresh_token
#' )
#' }
#'
auth_from_secret <- function(access_token = NULL, refresh_token = NULL) {
if (is.null(access_token) | is.null(refresh_token)) {
decrypted <- openssl::aes_cbc_decrypt(
readRDS(encrypt_creds_user_path()),
key = readRDS(key_encrypt_creds_path())
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
)
)
access_token <- unserialize(decrypted)[[1]]$access_token
refresh_token <- unserialize(decrypted)[[1]]$refresh_token

I'm doing this on my phone but this is what I'm suggesting you do. Bring the phrases from below up here (not sure if I typed these right) and declare the variables. Then below just specify access_token = access_token and same for refresh_token.

If this doesn't make sense, let me know and I'll send a commit later.

}

credentials <- list(
access_token = unserialize(decrypted)[[1]]$access_token,
Copy link
Contributor

Choose a reason for hiding this comment

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

If user provides the access_token and refresh_token as arguments, then this line won't be able to find decrypted object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh right! We need to have this defined as access_token and refresh_token above in the if statement

expires_in = 3599L,
refresh_token = unserialize(decrypted)[[1]]$refresh_token,
Copy link
Contributor

@howardbaik howardbaik Aug 9, 2023

Choose a reason for hiding this comment

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

Same comment as above: If user provides the access_token and refresh_token as arguments, then this line won't be able to find decrypted object.

scope = scopes_list,
token_type = "Bearer"
)

token <- httr::oauth2.0_token(
endpoint = app_set_up()$endpoint,
app = app_set_up()$app,
scope = scopes_list,
credentials = credentials
)

set_token(token)
return(invisible(token))
}

# This sets up the app creds no matter which way authorization is called
app_set_up <- function() {
decrypted <- openssl::aes_cbc_decrypt(
readRDS(encrypt_creds_path()),
key = readRDS(key_encrypt_creds_path())
)

app <- oauth_app(
appname = "ottrpal",
key = unserialize(decrypted)$client_id,
secret = unserialize(decrypted)$client_secret
)
endpoint <- oauth_endpoints("google")

return(list(app = app, endpoint = endpoint))
}
29 changes: 29 additions & 0 deletions R/data.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

#' Get file path to an key encryption RDS
key_encrypt_creds_path <- function() {
list.files(
pattern = "encrypt_pass.rds",
recursive = TRUE,
system.file("extdata", package = "ottrpal"),
full.names = TRUE
)
}
#' Get file path to an encrypted credentials RDS
encrypt_creds_path <- function() {
list.files(
pattern = "encrypt.rds",
recursive = TRUE,
system.file("extdata", package = "ottrpal"),
full.names = TRUE
)
}

#' Get file path to an default credentials RDS
encrypt_creds_user_path <- function() {
list.files(
pattern = "encrypted_default_user_creds.rds",
recursive = TRUE,
system.file("extdata", package = "ottrpal"),
full.names = TRUE
)
}
9 changes: 7 additions & 2 deletions R/notes_to_fig_alt.R
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,13 @@ extract_object_id = function(slide_url, token = NULL) {

# if token not provided, fetch token
if (is.null(token)) {

token_try <- try(get_token(), silent = TRUE)

# We will supply credentials if none can be grabbed by get_token()
if (is.null(token_try)) {
auth_from_secret()
}
token <- get_token()
} # else user provides token

Expand All @@ -317,8 +324,6 @@ extract_object_id = function(slide_url, token = NULL) {
result_list$slides$objectId
}



#' Retrieve Speaker Notes and their corresponding Object (Slide) IDs from a Google Slides presentation
#'
#' Google Slides API calls a presentation slide ID as an 'object ID'.
Expand Down
Binary file added inst/extdata/tmp/encrypt.rds
Binary file not shown.
Binary file added inst/extdata/tmp/encrypt_pass.rds
Binary file not shown.
Binary file added inst/extdata/tmp/encrypted_default_user_creds.rds
Binary file not shown.
32 changes: 32 additions & 0 deletions man/auth_from_secret.Rd

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

32 changes: 8 additions & 24 deletions man/authorize.Rd

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

11 changes: 11 additions & 0 deletions man/encrypt_creds_path.Rd

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

11 changes: 11 additions & 0 deletions man/encrypt_creds_user_path.Rd

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

11 changes: 11 additions & 0 deletions man/key_encrypt_creds_path.Rd

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