From cc842c316d0ea896c977bc71b991ac0331300449 Mon Sep 17 00:00:00 2001 From: Kevin Ushey Date: Wed, 28 Apr 2021 16:29:00 -0700 Subject: [PATCH 1/4] add 'key' parameter to askForSecret --- R/dialogs.R | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/R/dialogs.R b/R/dialogs.R index deaf4fb..1b99a73 100644 --- a/R/dialogs.R +++ b/R/dialogs.R @@ -89,6 +89,10 @@ showQuestion <- function(title, message, ok = NULL, cancel = NULL) { #' #' @param title The title to display in the dialog box. #' +#' @param key An optional key. When provided, RStudio will check to see if +#' an environment variable of the name `RSTUDIOAPI_SECRET_` is defined; +#' if so, that environment variable will be used to supply the secret. +#' #' @note The \code{askForSecret} function was added in version 1.1.419 of #' RStudio. #' @@ -96,7 +100,27 @@ showQuestion <- function(title, message, ok = NULL, cancel = NULL) { askForSecret <- function( name, message = paste(name, ":", sep = ""), - title = paste(name, "Secret")) { + title = paste(name, "Secret"), + key = NULL) { + + if (!is.null(key)) { + + # build full name of environment variable + name <- paste("RSTUDIOAPI_SECRET", toupper(key), sep = "_") + + # check for a definition + value <- Sys.getenv(name, unset = NA) + if (!is.na(value)) + return(value) + + # for non-interactive sessions, give a warning; otherwise, + # fall through an attempt to ask for a password + if (!interactive() && !isChildProcess()) { + fmt <- "The environment variable '%s' is required by this application, but is unset." + msg <- sprintf(fmt, name) + warning(msg) + } + } if (hasFun("askForSecret") || isChildProcess()) { callFun("askForSecret", name, title, message) From 6d960444479e6a5311f9aaff926da0d5e297f8d2 Mon Sep 17 00:00:00 2001 From: Kevin Ushey Date: Fri, 30 Apr 2021 09:58:58 -0700 Subject: [PATCH 2/4] add 'key' to askForPassword as well --- R/dialogs.R | 56 ----------------------- R/secrets.R | 102 ++++++++++++++++++++++++++++++++++++++++++ R/stubs.R | 26 ----------- man/askForPassword.Rd | 10 ++++- man/askForSecret.Rd | 11 ++++- 5 files changed, 119 insertions(+), 86 deletions(-) create mode 100644 R/secrets.R diff --git a/R/dialogs.R b/R/dialogs.R index 1b99a73..89ca224 100644 --- a/R/dialogs.R +++ b/R/dialogs.R @@ -73,59 +73,3 @@ showPrompt <- function(title, message, default = NULL) { showQuestion <- function(title, message, ok = NULL, cancel = NULL) { callFun("showQuestion", title, message, ok, cancel) } - - - -#' Prompt user for secret -#' -#' Request a secret from the user. If the `keyring` package is installed, it -#' will be used to cache requested secrets. -#' -#' -#' @param name The name of the secret. -#' -#' @param message A character vector with the contents to display in the main -#' dialog area. -#' -#' @param title The title to display in the dialog box. -#' -#' @param key An optional key. When provided, RStudio will check to see if -#' an environment variable of the name `RSTUDIOAPI_SECRET_` is defined; -#' if so, that environment variable will be used to supply the secret. -#' -#' @note The \code{askForSecret} function was added in version 1.1.419 of -#' RStudio. -#' -#' @export -askForSecret <- function( - name, - message = paste(name, ":", sep = ""), - title = paste(name, "Secret"), - key = NULL) { - - if (!is.null(key)) { - - # build full name of environment variable - name <- paste("RSTUDIOAPI_SECRET", toupper(key), sep = "_") - - # check for a definition - value <- Sys.getenv(name, unset = NA) - if (!is.na(value)) - return(value) - - # for non-interactive sessions, give a warning; otherwise, - # fall through an attempt to ask for a password - if (!interactive() && !isChildProcess()) { - fmt <- "The environment variable '%s' is required by this application, but is unset." - msg <- sprintf(fmt, name) - warning(msg) - } - } - - if (hasFun("askForSecret") || isChildProcess()) { - callFun("askForSecret", name, title, message) - } else { - askForPassword(message) - } - -} diff --git a/R/secrets.R b/R/secrets.R new file mode 100644 index 0000000..d5db0c4 --- /dev/null +++ b/R/secrets.R @@ -0,0 +1,102 @@ + +# returns the secret associated with a key 'key', or NULL +# if no secret is available +retrieveSecret <- function(key, label) { + + if (is.null(key)) + return(NULL) + + # build full name of environment variable + name <- paste("RSTUDIOAPI_SECRET", toupper(key), sep = "_") + + # check for a definition + value <- Sys.getenv(name, unset = NA) + if (!is.na(value)) + return(value) + + # for non-interactive sessions, give a warning; otherwise, + # fall through an attempt to ask for a password + if (!interactive() && !isChildProcess()) { + fmt <- "The %s associated with key '%s' is not set or could not be retrieved." + msg <- sprintf(fmt, label, key) + warning(msg) + } + +} + +#' Ask the user for a password interactively +#' +#' Ask the user for a password interactively. +#' +#' RStudio also sets the global \code{askpass} option to the +#' \code{rstudioapi::askForPassword} function so that it can be invoked in a +#' front-end independent manner. +#' +#' @param prompt The prompt to be shown to the user. +#' +#' @param key An optional key. When provided, RStudio will check to see if +#' an environment variable of the name `RSTUDIOAPI_SECRET_` is defined; +#' if so, that environment variable will be used to supply the password. +#' If the variable is unset, then (in interactive sessions) the user will +#' be prompted for a password; otherwise, a warning will be shown. +#' +#' @note The \code{askForPassword} function was added in version 0.99.853 of +#' RStudio. +#' +#' @examples +#' +#' \dontrun{ +#' rstudioapi::askForPassword("Please enter your password") +#' } +#' +#' @export askForPassword +askForPassword <- function(prompt = "Please enter your password", + key = NULL) +{ + password <- retrieveSecret(key, "password") + if (!is.null(password)) + return(password) + + callFun("askForPassword", prompt) +} + +#' Prompt user for secret +#' +#' Request a secret from the user. If the `keyring` package is installed, it +#' will be used to cache requested secrets. +#' +#' @param name The name of the secret. +#' +#' @param message A character vector with the contents to display in the main +#' dialog area. +#' +#' @param title The title to display in the dialog box. +#' +#' @param key An optional key. When provided, RStudio will check to see if +#' an environment variable of the name `RSTUDIOAPI_SECRET_` is defined; +#' if so, that environment variable will be used to supply the secret. +#' If the variable is unset, then (in interactive sessions) the user will +#' be prompted for a password; otherwise, a warning will be shown. +#' +#' @note The \code{askForSecret} function was added in version 1.1.419 of +#' RStudio. +#' +#' @export +askForSecret <- function( + name, + message = paste(name, ":", sep = ""), + title = paste(name, "Secret"), + key = NULL) { + + secret <- retrieveSecret(key, "secret") + if (!is.null(secret)) + return(secret) + + # use 'askForSecret' if available + if (hasFun("askForSecret") || isChildProcess()) + return(callFun("askForSecret", name, title, message)) + + # otherwise, fall back to askForPassword + askForPassword(message) + +} diff --git a/R/stubs.R b/R/stubs.R index 4eb717a..cc7fc55 100644 --- a/R/stubs.R +++ b/R/stubs.R @@ -249,32 +249,6 @@ navigateToFile <- function(file = character(0), } -#' Ask the user for a password interactively -#' -#' Ask the user for a password interactively. -#' -#' RStudio also sets the global \code{askpass} option to the -#' \code{rstudioapi::askForPassword} function so that it can be invoked in a -#' front-end independent manner. -#' -#' @param prompt The prompt to be shown to the user. -#' -#' @note The \code{askForPassword} function was added in version 0.99.853 of -#' RStudio. -#' -#' @examples -#' -#' \dontrun{ -#' rstudioapi::askForPassword("Please enter your password") -#' } -#' -#' @export askForPassword -askForPassword <- function(prompt = "Please enter your password") { - callFun("askForPassword", prompt) -} - - - #' Retrieve path to active RStudio project #' #' Get the path to the active RStudio project (if any). If the path contains diff --git a/man/askForPassword.Rd b/man/askForPassword.Rd index dc280ef..1e23f98 100644 --- a/man/askForPassword.Rd +++ b/man/askForPassword.Rd @@ -1,13 +1,19 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/stubs.R +% Please edit documentation in R/secrets.R \name{askForPassword} \alias{askForPassword} \title{Ask the user for a password interactively} \usage{ -askForPassword(prompt = "Please enter your password") +askForPassword(prompt = "Please enter your password", key = NULL) } \arguments{ \item{prompt}{The prompt to be shown to the user.} + +\item{key}{An optional key. When provided, RStudio will check to see if +an environment variable of the name \verb{RSTUDIOAPI_SECRET_} is defined; +if so, that environment variable will be used to supply the password. +If the variable is unset, then (in interactive sessions) the user will +be prompted for a password; otherwise, a warning will be shown.} } \description{ Ask the user for a password interactively. diff --git a/man/askForSecret.Rd b/man/askForSecret.Rd index bf5dde8..a95cd81 100644 --- a/man/askForSecret.Rd +++ b/man/askForSecret.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/dialogs.R +% Please edit documentation in R/secrets.R \name{askForSecret} \alias{askForSecret} \title{Prompt user for secret} @@ -7,7 +7,8 @@ askForSecret( name, message = paste(name, ":", sep = ""), - title = paste(name, "Secret") + title = paste(name, "Secret"), + key = NULL ) } \arguments{ @@ -17,6 +18,12 @@ askForSecret( dialog area.} \item{title}{The title to display in the dialog box.} + +\item{key}{An optional key. When provided, RStudio will check to see if +an environment variable of the name \verb{RSTUDIOAPI_SECRET_} is defined; +if so, that environment variable will be used to supply the secret. +If the variable is unset, then (in interactive sessions) the user will +be prompted for a password; otherwise, a warning will be shown.} } \description{ Request a secret from the user. If the \code{keyring} package is installed, it From c9cb6d6cbc91b4e7181c587dfd3e1a78905a8c22 Mon Sep 17 00:00:00 2001 From: Kevin Ushey Date: Thu, 13 May 2021 12:39:12 -0700 Subject: [PATCH 3/4] 'key' -> 'tag' --- R/secrets.R | 85 ++++++++++++++++++++++++++----------------- man/askForPassword.Rd | 25 ++++++++----- man/askForSecret.Rd | 22 +++++------ 3 files changed, 78 insertions(+), 54 deletions(-) diff --git a/R/secrets.R b/R/secrets.R index d5db0c4..e35bdba 100644 --- a/R/secrets.R +++ b/R/secrets.R @@ -1,13 +1,14 @@ # returns the secret associated with a key 'key', or NULL # if no secret is available -retrieveSecret <- function(key, label) { +retrieveSecret <- function(tag, label) { - if (is.null(key)) + # nothing to do if we don't have a key + if (is.null(tag)) return(NULL) # build full name of environment variable - name <- paste("RSTUDIOAPI_SECRET", toupper(key), sep = "_") + name <- paste("RSTUDIOAPI_SECRET", toupper(tag), sep = "_") # check for a definition value <- Sys.getenv(name, unset = NA) @@ -17,46 +18,65 @@ retrieveSecret <- function(key, label) { # for non-interactive sessions, give a warning; otherwise, # fall through an attempt to ask for a password if (!interactive() && !isChildProcess()) { - fmt <- "The %s associated with key '%s' is not set or could not be retrieved." - msg <- sprintf(fmt, label, key) + fmt <- "The %s associated with tag '%s' is not set or could not be retrieved." + msg <- sprintf(fmt, label, tag) warning(msg) } } +#' @param name The name to associate with the secret. This name will be used +#' to locate the requested secret; e.g. when requested via the `keyring` +#' package. +#' +#' @param prompt The prompt to be shown to the user. +#' +#' @param tag An optional tag, used to assist `rstudioapi` in reading secrets +#' in non-interactive \R sessions. Currently, when provided, `rstudioapi` +#' will check if an environment variable of the name `RSTUDIOAPI_SECRET_` +#' is defined; if so, that environment variable will be used to supply the +#' password. If the variable is unset, then (in interactive sessions) the user +#' will be prompted for a password; otherwise, a warning will be shown. +#' +#' @name dialog-params +NULL + #' Ask the user for a password interactively #' -#' Ask the user for a password interactively. +#' Ask the user for a password interactively. A dialog requesting a password +#' from the user will be shown, and the value of the retrieved password will +#' be returned. #' #' RStudio also sets the global \code{askpass} option to the -#' \code{rstudioapi::askForPassword} function so that it can be invoked in a +#' [askForPassword()] function so that it can be invoked in a #' front-end independent manner. #' -#' @param prompt The prompt to be shown to the user. -#' -#' @param key An optional key. When provided, RStudio will check to see if -#' an environment variable of the name `RSTUDIOAPI_SECRET_` is defined; -#' if so, that environment variable will be used to supply the password. -#' If the variable is unset, then (in interactive sessions) the user will -#' be prompted for a password; otherwise, a warning will be shown. -#' +#' @inheritParams dialog-params +#' #' @note The \code{askForPassword} function was added in version 0.99.853 of #' RStudio. #' #' @examples #' #' \dontrun{ -#' rstudioapi::askForPassword("Please enter your password") +#' rstudioapi::askForPassword("Please enter your password:") #' } #' #' @export askForPassword -askForPassword <- function(prompt = "Please enter your password", - key = NULL) +askForPassword <- function(prompt = "Please enter your password:", + name = NULL, + tag = NULL) { - password <- retrieveSecret(key, "password") + # if 'name' was supplied, delegate to askForSecret + if (!is.null(name)) + return(askForSecret(name = name, message = prompt, tag = tag)) + + # try to retrieve password non-interactively + password <- retrieveSecret(tag, "password") if (!is.null(password)) return(password) + # request password from user callFun("askForPassword", prompt) } @@ -65,30 +85,30 @@ askForPassword <- function(prompt = "Please enter your password", #' Request a secret from the user. If the `keyring` package is installed, it #' will be used to cache requested secrets. #' -#' @param name The name of the secret. +#' @inheritParams dialog-params #' #' @param message A character vector with the contents to display in the main #' dialog area. #' #' @param title The title to display in the dialog box. #' -#' @param key An optional key. When provided, RStudio will check to see if -#' an environment variable of the name `RSTUDIOAPI_SECRET_` is defined; -#' if so, that environment variable will be used to supply the secret. -#' If the variable is unset, then (in interactive sessions) the user will -#' be prompted for a password; otherwise, a warning will be shown. -#' #' @note The \code{askForSecret} function was added in version 1.1.419 of #' RStudio. #' #' @export -askForSecret <- function( - name, - message = paste(name, ":", sep = ""), - title = paste(name, "Secret"), - key = NULL) { +askForSecret <- function(name, + message = NULL, + title = "Secret", + tag = NULL) +{ + # resolve 'message' argument + if (is.null(message)) { + fmt <- "Please enter a secret to associate with key '%s':" + message <- sprintf(fmt, name) + } - secret <- retrieveSecret(key, "secret") + # try to retrieve secret non-interactively + secret <- retrieveSecret(tag, "secret") if (!is.null(secret)) return(secret) @@ -98,5 +118,4 @@ askForSecret <- function( # otherwise, fall back to askForPassword askForPassword(message) - } diff --git a/man/askForPassword.Rd b/man/askForPassword.Rd index 1e23f98..05c431e 100644 --- a/man/askForPassword.Rd +++ b/man/askForPassword.Rd @@ -4,23 +4,30 @@ \alias{askForPassword} \title{Ask the user for a password interactively} \usage{ -askForPassword(prompt = "Please enter your password", key = NULL) +askForPassword(prompt = "Please enter your password:", name = NULL, tag = NULL) } \arguments{ \item{prompt}{The prompt to be shown to the user.} -\item{key}{An optional key. When provided, RStudio will check to see if -an environment variable of the name \verb{RSTUDIOAPI_SECRET_} is defined; -if so, that environment variable will be used to supply the password. -If the variable is unset, then (in interactive sessions) the user will -be prompted for a password; otherwise, a warning will be shown.} +\item{name}{The name to associate with the secret. This name will be used +to locate the requested secret; e.g. when requested via the \code{keyring} +package.} + +\item{tag}{An optional tag, used to assist \code{rstudioapi} in reading secrets +in non-interactive \R sessions. Currently, when provided, \code{rstudioapi} +will check if an environment variable of the name \verb{RSTUDIOAPI_SECRET_} +is defined; if so, that environment variable will be used to supply the +password. If the variable is unset, then (in interactive sessions) the user +will be prompted for a password; otherwise, a warning will be shown.} } \description{ -Ask the user for a password interactively. +Ask the user for a password interactively. A dialog requesting a password +from the user will be shown, and the value of the retrieved password will +be returned. } \details{ RStudio also sets the global \code{askpass} option to the -\code{rstudioapi::askForPassword} function so that it can be invoked in a +\code{\link[=askForPassword]{askForPassword()}} function so that it can be invoked in a front-end independent manner. } \note{ @@ -30,7 +37,7 @@ RStudio. \examples{ \dontrun{ -rstudioapi::askForPassword("Please enter your password") +rstudioapi::askForPassword("Please enter your password:") } } diff --git a/man/askForSecret.Rd b/man/askForSecret.Rd index a95cd81..a0cc0c6 100644 --- a/man/askForSecret.Rd +++ b/man/askForSecret.Rd @@ -4,26 +4,24 @@ \alias{askForSecret} \title{Prompt user for secret} \usage{ -askForSecret( - name, - message = paste(name, ":", sep = ""), - title = paste(name, "Secret"), - key = NULL -) +askForSecret(name, message = NULL, title = "Secret", tag = NULL) } \arguments{ -\item{name}{The name of the secret.} +\item{name}{The name to associate with the secret. This name will be used +to locate the requested secret; e.g. when requested via the \code{keyring} +package.} \item{message}{A character vector with the contents to display in the main dialog area.} \item{title}{The title to display in the dialog box.} -\item{key}{An optional key. When provided, RStudio will check to see if -an environment variable of the name \verb{RSTUDIOAPI_SECRET_} is defined; -if so, that environment variable will be used to supply the secret. -If the variable is unset, then (in interactive sessions) the user will -be prompted for a password; otherwise, a warning will be shown.} +\item{tag}{An optional tag, used to assist \code{rstudioapi} in reading secrets +in non-interactive \R sessions. Currently, when provided, \code{rstudioapi} +will check if an environment variable of the name \verb{RSTUDIOAPI_SECRET_} +is defined; if so, that environment variable will be used to supply the +password. If the variable is unset, then (in interactive sessions) the user +will be prompted for a password; otherwise, a warning will be shown.} } \description{ Request a secret from the user. If the \code{keyring} package is installed, it From c42f4d1014b1d5656207ac380c71b2dbc638d1d5 Mon Sep 17 00:00:00 2001 From: Kevin Ushey Date: Thu, 13 May 2021 12:47:28 -0700 Subject: [PATCH 4/4] key -> name --- R/secrets.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/secrets.R b/R/secrets.R index e35bdba..669e5aa 100644 --- a/R/secrets.R +++ b/R/secrets.R @@ -103,7 +103,7 @@ askForSecret <- function(name, { # resolve 'message' argument if (is.null(message)) { - fmt <- "Please enter a secret to associate with key '%s':" + fmt <- "Please enter a secret to associate with name '%s':" message <- sprintf(fmt, name) }