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

Integrate with lifecycle package #651

Merged
merged 15 commits into from
Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from 12 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
4 changes: 3 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ Imports:
promises (>= 1.1.0),
sodium,
swagger (> 3.20.3),
magrittr
magrittr,
lifecycle
LazyData: TRUE
ByteCompile: TRUE
Suggests:
Expand Down Expand Up @@ -82,3 +83,4 @@ Collate:
'utils-pipe.R'
'validate_api_spec.R'
'zzz.R'
RdMacros: lifecycle
5 changes: 4 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export(do_remove_api)
export(do_remove_forward)
export(forward)
export(getCharacterSet)
export(get_character_set)
export(include_file)
export(include_html)
export(include_md)
Expand Down Expand Up @@ -57,7 +58,7 @@ export(pr_set_parsers)
export(pr_set_serializer)
export(pr_set_ui)
export(pr_set_ui_callback)
export(randomCookieKey)
export(random_cookie_key)
export(register_parser)
export(register_serializer)
export(register_ui)
Expand Down Expand Up @@ -87,12 +88,14 @@ export(serializer_tsv)
export(serializer_unboxed_json)
export(serializer_yaml)
export(sessionCookie)
export(session_cookie)
export(validate_api_spec)
import(R6)
import(promises)
import(stringi)
importFrom(jsonlite,parse_json)
importFrom(jsonlite,toJSON)
importFrom(lifecycle,deprecated)
importFrom(magrittr,"%>%")
importFrom(stats,runif)
importFrom(utils,installed.packages)
Expand Down
8 changes: 6 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ plumber 1.0.0

* Secret session cookies are now encrypted using `sodium`.
All prior `req$session` information will be lost.
Please see `?sessionCookie` for more information.
Please see `?session_cookie` for more information.
(#404)

* Session cookies set the `HttpOnly` flag by default to mitigate cross-site scripting (XSS).
Please see `?sessionCookie` for more information.
Please see `?session_cookie` for more information.
(#404)

* Wrap `jsonlite::fromJSON` to ensure that `jsonlite` never reads
Expand Down Expand Up @@ -42,6 +42,10 @@ plumber 1.0.0

* `addSerializer()` has been deprecated in favor of `register_serializer()` (#584)

* `getCharacterSet()` has been deprecated in favor of `get_character_set()`.
* `randomCookieKey()` has been deprecated in favor of `random_cookie_key()`.
* `sessionCookie()` has been deprecated in favor of `session_cookie()`.

### New features

* Serializer functions can now return `PlumberEndpoint` `preexec` and `postexec` hooks in addition to a `serializer` function by using `endpoint_serializer()`. This allows for image serializers to turn on their corresponding graphics device before the route executes and turn the graphics device off after the route executes. (#630)
Expand Down
8 changes: 4 additions & 4 deletions R/content-types.R
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ getContentType <- function(ext, defaultType='application/octet-stream') {
}

#' Request character set
#' @param contentType Request Content-Type header
#' @param content_type Request Content-Type header
#' @return Default to `UTF-8`. Otherwise return `charset` defined in request header.
#' @export
getCharacterSet <- function(contentType = NULL){
if (is.null(contentType)) return("UTF-8")
stri_match_first_regex(paste(contentType,"; charset=UTF-8"), "charset=([^;\\s]*)")[,2]
get_character_set <- function(content_type = NULL) {
if (is.null(content_type)) return("UTF-8")
stri_match_first_regex(paste(content_type,"; charset=UTF-8"), "charset=([^;\\s]*)")[,2]
}
36 changes: 33 additions & 3 deletions R/deprecated.R
Original file line number Diff line number Diff line change
@@ -1,9 +1,39 @@
#' Register a Serializer
#'
#' Use [register_serializer()] in favor of addSerializer
#' Deprecated functions
#'
#' @describeIn deprecated See [register_serializer()]
#' @export
#' @keywords internal
addSerializer <- function(name, serializer, verbose = TRUE) {
lifecycle::deprecate_soft("1.0.0", "addSerializer()", "register_serializer()")
schloerke marked this conversation as resolved.
Show resolved Hide resolved

register_serializer(name = name, serializer = serializer, verbose = verbose)
}

#' @describeIn deprecated See [get_character_set()]
#' @export
getCharacterSet <- function(contentType = NULL) {
lifecycle::deprecate_soft("1.0.0", "getCharacterSet()", "get_character_set()")
get_character_set(content_type = contentType)
}


#' @describeIn deprecated See [session_cookie()]
#' @export
sessionCookie <- function(
key,
name = "plumber",
expiration = FALSE,
http = TRUE,
secure = FALSE,
sameSite = FALSE
) {
lifecycle::deprecate_soft("1.0.0", "sessionCookie()", "session_cookie()")
session_cookie(
key = key,
name = name,
expiration = expiration,
http = http,
secure = secure,
same_site = sameSite
)
}
4 changes: 2 additions & 2 deletions R/parse-body.R
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ parser_picker <- function(content_type, first_byte, filename = NULL, parsers = N
#' # `content-type` header is mostly used to look up charset and adjust encoding
#' parser_dcf <- function(...) {
#' function(value, content_type = "text/x-dcf", ...) {
#' charset <- getCharacterSet(content_type)
#' charset <- get_character_set(content_type)
#' value <- rawToChar(value)
#' Encoding(value) <- charset
#' read.dcf(value, ...)
Expand Down Expand Up @@ -358,7 +358,7 @@ parser_json <- function(...) {
parser_text <- function(parse_fn = identity) {
stopifnot(is.function(parse_fn))
function(value, content_type = NULL, ...) {
charset <- getCharacterSet(content_type)
charset <- get_character_set(content_type)
txt_value <- rawToChar(value)
Encoding(txt_value) <- charset
parse_fn(txt_value)
Expand Down
2 changes: 1 addition & 1 deletion R/plumb-block.R
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ plumbBlock <- function(lineNum, file, envir = parent.frame()){
if (!is.na(shortSerMat[1,2])) {
s <- stri_trim_both(shortSerMat[1,2])
.Deprecated(msg = paste0(
"Plumber tag `#* @", s, "` is deprecated.\n",
"Plumber tag `#* @", s, "` is soft deprecated.\n",
schloerke marked this conversation as resolved.
Show resolved Hide resolved
"Use `#* @serializer ", s, "` instead."
))
if (!is.null(serializer)){
Expand Down
36 changes: 25 additions & 11 deletions R/plumber-response.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ PlumberResponse <- R6Class(
body <- ""
}

charset <- getCharacterSet(h$HTTP_CONTENT_TYPE)
charset <- get_character_set(h$HTTP_CONTENT_TYPE)
if (is.character(body)) {
Encoding(body) <- charset
}
Expand All @@ -34,16 +34,30 @@ PlumberResponse <- R6Class(
)
},
# TODO if name and value are a vector of same length, call set cookie many times
setCookie = function(name, value, path, expiration = FALSE, http = FALSE, secure = FALSE, sameSite=FALSE) {
self$setHeader("Set-Cookie", cookieToStr(name, value, path, expiration, http, secure, sameSite))
setCookie = function(name, value, path, expiration = FALSE, http = FALSE, secure = FALSE, same_site = FALSE) {
self$setHeader("Set-Cookie", cookieToStr(
name = name,
value = value,
path = path,
expiration = expiration,
http = http,
secure = secure,
same_site = same_site
))
},
removeCookie = function(name, path, http = FALSE, secure = FALSE, sameSite = FALSE, ...) {
self$setHeader("Set-Cookie", removeCookieStr(name, path, http, secure, sameSite))
removeCookie = function(name, path, http = FALSE, secure = FALSE, same_site = FALSE, ...) {
self$setHeader("Set-Cookie", removeCookieStr(
name = name,
path = path,
http = http,
secure = secure,
same_site = same_site
))
}
)
)

removeCookieStr <- function(name, path, http = FALSE, secure = FALSE, sameSite = FALSE) {
removeCookieStr <- function(name, path, http = FALSE, secure = FALSE, same_site = FALSE) {
str <- paste0(name, "=; ")
if (!missing(path)){
str <- paste0(str, "Path=", path, "; ")
Expand All @@ -55,8 +69,8 @@ removeCookieStr <- function(name, path, http = FALSE, secure = FALSE, sameSite =
str <- paste0(str, "Secure; ")
}

if (!missing(sameSite) && is.character(sameSite)) {
str <- paste0(str, "SameSite=", sameSite, "; ")
if (!missing(same_site) && is.character(same_site)) {
str <- paste0(str, "SameSite=", same_site, "; ")
}

str <- paste0(str, "Expires=Thu, 01 Jan 1970 00:00:00 GMT")
Expand All @@ -71,7 +85,7 @@ cookieToStr <- function(
expiration = FALSE,
http = FALSE,
secure = FALSE,
sameSite = FALSE,
same_site = FALSE,
now = Sys.time() # used for testing. Should not be used in regular code.
){
val <- httpuv::encodeURIComponent(as.character(value))
Expand All @@ -89,8 +103,8 @@ cookieToStr <- function(
str <- paste0(str, "Secure; ")
}

if (!missing(sameSite) && is.character(sameSite)) {
str <- paste0(str, "SameSite=", sameSite, "; ")
if (!missing(same_site) && is.character(same_site)) {
str <- paste0(str, "SameSite=", same_site, "; ")
}

if (!missing(expiration)){
Expand Down
27 changes: 12 additions & 15 deletions R/plumber.R
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,13 @@ plumber <- R6Class(
#' @param debug Deprecated. See `$set_debug()`
#' @param swagger Deprecated. See `$set_ui(ui)` or `$set_api_spec()`
#' @param swaggerCallback Deprecated. See `$set_ui_callback()`
#' @importFrom lifecycle deprecated
run = function(
host = '127.0.0.1',
port = getOption('plumber.port', NULL),
swagger = stop("deprecated"),
debug = stop("deprecated"),
swaggerCallback = stop("deprecated")
swagger = deprecated(),
debug = deprecated(),
swaggerCallback = deprecated()
) {

if (isTRUE(private$disable_run)) {
Expand All @@ -221,35 +222,31 @@ plumber <- R6Class(

# Legacy support for RStudio pro products.
# Checks must be kept for >= 2 yrs after plumber v1.0.0 release date
if (!missing(debug)) {
message("`$run(debug)` has been deprecated in v1.0.0 and will be removed in a coming release. Please use `$set_debug(debug)`")
if (lifecycle::is_present(debug)) {
lifecycle::deprecate_warn("1.0.0", "run(debug = )", "set_debug(debug = )")
self$set_debug(debug)
}
if (!missing(swagger)) {
if (lifecycle::is_present(swagger)) {
if (is.function(swagger)) {
# between v0.4.6 and v1.0.0
message("`$run(swagger)` has been deprecated in v1.0.0 and will be removed in a coming release. To alter the swagger spec, please use `$set_api_spec(api)`")
lifecycle::deprecate_warn("1.0.0", "run(swagger = )", "set_api_spec(api = )")
self$set_api_spec(swagger)
# spec is now enabled by default. Do not alter
} else {
if (isTRUE(private$ui_info$has_not_been_set)) {
# <= v0.4.6
message("`$run(swagger)` has been deprecated in v1.0.0 and will be removed in a coming release. Please use `$set_ui(ui)`")
lifecycle::deprecate_warn("1.0.0", "run(swagger = )", "set_ui(ui = )")
self$set_ui(swagger)
} else {
# $set_ui() has been called (other than during initialization).
# Believe that it is the correct behavior
# Warn about updating the run method
message(
"`$run(swagger)` has been deprecated in v1.0.0 and will be removed in a coming release.\n",
"The plumber UI has already been set. Ignoring `swagger` parameter.\n",
"Please update your `$run()` method."
)
lifecycle::deprecate_warn("1.0.0", "run(swagger = )", details = "The plumber UI has already been set. Ignoring `swagger` parameter.")
}
}
}
if (!missing(swaggerCallback)) {
message("`$run(swaggerCallback)` has been deprecated in v1.0.0 and will be removed in a coming release. Please use `$set_ui_callback(callback)`")
if (lifecycle::is_present(swaggerCallback)) {
.Deprecated(msg = "`$run(swaggerCallback)` has been deprecated in v1.0.0 and will be removed in a coming release. Please use `$set_ui_callback(callback)`")
schloerke marked this conversation as resolved.
Show resolved Hide resolved
self$set_ui_callback(swaggerCallback)
}

Expand Down
14 changes: 7 additions & 7 deletions R/pr.R
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ pr_hooks <- function(pr,
#' @template param_pr
#' @param key The secret key to use. This must be consistent across all R sessions
#' where you want to save/restore encrypted cookies. It should be produced using
#' \code{\link{randomCookieKey}}. Please see the "Storing secure keys" section for more details
#' \code{\link{random_cookie_key}}. Please see the "Storing secure keys" section for more details
#' complex character string to bolster security.
#' @param name The name of the cookie in the user's browser.
#' @param expiration A number representing the number of seconds into the future
Expand All @@ -342,10 +342,10 @@ pr_hooks <- function(pr,
#' Defaults to \code{TRUE}.
#' @param secure Boolean that adds the \code{Secure} cookie flag. This should be set
#' when the route is eventually delivered over \href{https://en.wikipedia.org/wiki/HTTPS}{HTTPS}.
#' @param sameSite A character specifying the SameSite policy to attach to the cookie.
#' @param same_site A character specifying the SameSite policy to attach to the cookie.
#' If specified, one of the following values should be given: "Strict", "Lax", or "None".
#' If "None" is specified, then the \code{secure} flag MUST also be set for the modern browsers to
#' accept the cookie. An error will be returned if \code{sameSite = "None"} and \code{secure = FALSE}.
#' accept the cookie. An error will be returned if \code{same_site = "None"} and \code{secure = FALSE}.
#' If not specified or a non-character is given, no SameSite policy is attached to the cookie.
#' @seealso \itemize{
#' \item \href{https://github.com/jeroen/sodium}{'sodium'}: R bindings to 'libsodium'
Expand All @@ -358,7 +358,7 @@ pr_hooks <- function(pr,
#' \dontrun{
#'
#' ## Set secret key using `keyring` (preferred method)
#' keyring::key_set_with_value("plumber_api", password = plumber::randomCookieKey())
#' keyring::key_set_with_value("plumber_api", password = plumber::random_cookie_key())
#'
#'
#' pr() %>%
Expand All @@ -382,7 +382,7 @@ pr_hooks <- function(pr,
#'
#' ## Save key to a local file
#' pswd_file <- "normal_file.txt"
#' cat(plumber::randomCookieKey(), file = pswd_file)
#' cat(plumber::random_cookie_key(), file = pswd_file)
#' # Make file read-only
#' Sys.chmod(pswd_file, mode = "0600")
#'
Expand All @@ -408,10 +408,10 @@ pr_cookie <- function(pr,
expiration = FALSE,
http = TRUE,
secure = FALSE,
sameSite = FALSE) {
same_site = FALSE) {
validate_pr(pr)
pr$registerHooks(
sessionCookie(key = key, name = name, expiration = expiration, http = http, secure = secure, sameSite = sameSite)
session_cookie(key = key, name = name, expiration = expiration, http = http, secure = secure, same_site = same_site)
)
invisible(pr)
}
Expand Down
Loading