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

Add OAuth vignette & cache path customisation #286

Merged
merged 6 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export(last_request)
export(last_response)
export(local_mock)
export(multi_req_perform)
export(oauth_cache_path)
export(oauth_client)
export(oauth_client_req_auth)
export(oauth_client_req_auth_body)
Expand Down
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# httr2 (development version)

* New `oauth_cache_path()` returns the path that httr2 uses for caching OAuth
tokens. Additionally, you can now change the cache location by setting the
`HTTR2_OAUTH_CACHE` env var.

* New `vignette("oauth")` makes the details of OAuth usage easier to find
(#234).

* New `req_cookie_preserve()` lets you use a file to share cookies across
requests (#223).

Expand Down
7 changes: 5 additions & 2 deletions R/oauth-flow-auth-code.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#' The token is automatically cached (either in memory or on disk) to minimise
#' the number of times the flow is performed.
#'
#' Learn more about the overall flow in `vignette("oauth")`.
#'
#' # Security considerations
#'
#' The authorization code flow is used for both web applications and native
Expand All @@ -29,8 +31,9 @@
#' @inheritParams req_perform
#' @param cache_disk Should the access token be cached on disk? This reduces
#' the number of times that you need to re-authenticate at the cost of
#' storing access credentials on disk. Cached tokens are encrypted and
#' automatically deleted 30 days after creation.
#' storing access credentials on disk. Cached tokens are encrypted,
#' automatically deleted 30 days after creation, and stored in
#' [oauth_cache_path()].
#' @param cache_key If you want to cache multiple tokens per app, use this
#' key to disambiguate them.
#' @returns A modified HTTP [request].
Expand Down
2 changes: 2 additions & 0 deletions R/oauth-flow-client-credentials.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#' which is then used to authentication the request with [req_auth_bearer_token()].
#' The token is cached in memory.
#'
#' Learn more about the overall flow in `vignette("oauth")`.
#'
#' @export
#' @inheritParams req_perform
#' @inheritParams oauth_flow_client_credentials
Expand Down
2 changes: 2 additions & 0 deletions R/oauth-flow-device.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#' The token is automatically cached (either in memory or on disk) to minimise
#' the number of times the flow is performed.
#'
#' Learn more about the overall flow in `vignette("oauth")`.
#'
#' @export
#' @inheritParams oauth_flow_password
#' @inheritParams req_oauth_auth_code
Expand Down
2 changes: 2 additions & 0 deletions R/oauth-flow-jwt.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#' used to authenticate the request with [req_auth_bearer_token()].
#' The token is cached in memory.
#'
#' Learn more about the overall flow in `vignette("oauth")`.
#'
#' @export
#' @inheritParams req_perform
#' @inheritParams oauth_flow_bearer_jwt
Expand Down
2 changes: 2 additions & 0 deletions R/oauth-flow-password.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#' or on disk); the password is used once to get the token and is then
#' discarded.
#'
#' Learn more about the overall flow in `vignette("oauth")`.
#'
#' @export
#' @inheritParams oauth_flow_password
#' @inheritParams req_oauth_auth_code
Expand Down
2 changes: 2 additions & 0 deletions R/oauth-flow-refresh.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#' token. If this happens, `oauth_flow_refresh()` will warn, and you'll have to
#' update your stored refresh token.
#'
#' Learn more about the overall flow in `vignette("oauth")`.
#'
#' @export
#' @inheritParams req_perform
#' @inheritParams oauth_flow_refresh
Expand Down
20 changes: 18 additions & 2 deletions R/oauth.R
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ cache_mem <- function(client, key) {
)
}
cache_disk <- function(client, key) {
app_path <- file.path(rappdirs::user_cache_dir("httr2"), client$name)
app_path <- file.path(oauth_cache_path(), client$name)
dir.create(app_path, showWarnings = FALSE, recursive = TRUE)

path <- file.path(app_path, paste0(hash(key), "-token.rds.enc"))
Expand All @@ -109,10 +109,26 @@ cache_disk <- function(client, key) {
}

# Update req_oauth_auth_code() docs if change default from 30
cache_disk_prune <- function(days = 30, path = rappdirs::user_cache_dir("httr2")) {
cache_disk_prune <- function(days = 30, path = oauth_cache_path()) {
files <- dir(path, recursive = TRUE, full.names = TRUE, pattern = "-token\\.rds$")
mtime <- file.mtime(files)

old <- mtime < (Sys.time() - days * 86400)
unlink(files[old])
}

#' httr2 OAuth cache location
#'
#' When opted-in to, httr2 caches OAuth tokens in this directory. By default,
#' it uses a OS-standard cache directory, but, if needed, you can override the
#' location by setting the `HTTR2_OAUTH_CACHE` env var.
#'
#' @export
oauth_cache_path <- function() {
path <- Sys.getenv("HTTR2_OAUTH_CACHE")
if (path != "") {
return(path)
}

rappdirs::user_cache_dir("httr2")
}
10 changes: 5 additions & 5 deletions R/req-cache.R
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ req_cache <- function(req,
# Do I need to worry about hash collisions?
# No - even if the user stores a billion urls, the probably of a collision
# is ~ 1e-20: https://preshing.com/20110504/hash-collision-probabilities/
cache_path <- function(req, ext = ".rds") {
req_cache_path <- function(req, ext = ".rds") {
file.path(req$policies$cache_path, paste0(hash(req$url), ext))
}
cache_use_on_error <- function(req) {
Expand All @@ -91,26 +91,26 @@ cache_exists <- function(req) {
if (!req_policy_exists(req, "cache_path")) {
FALSE
} else {
file.exists(cache_path(req))
file.exists(req_cache_path(req))
}
}

# Callers responsibility to check that cache exists
cache_get <- function(req) {
path <- cache_path(req)
path <- req_cache_path(req)

touch(path)
readRDS(path)
}

cache_set <- function(req, resp) {
if (is_path(resp$body)) {
body_path <- cache_path(req, ".body")
body_path <- req_cache_path(req, ".body")
file.copy(resp$body, body_path, overwrite = TRUE)
resp$body <- new_path(body_path)
}

saveRDS(resp, cache_path(req, ".rds"))
saveRDS(resp, req_cache_path(req, ".rds"))
invisible()
}

Expand Down
7 changes: 7 additions & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,10 @@ reference:
contents:
- starts_with("oauth_")
- starts_with("jwt_")

articles:
- title: Using httr2
navbar: ~
contents:
- articles/wrapping-apis
- articles/oauth
13 changes: 13 additions & 0 deletions man/oauth_cache_path.Rd

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

7 changes: 5 additions & 2 deletions man/req_oauth_auth_code.Rd

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

2 changes: 2 additions & 0 deletions man/req_oauth_bearer_jwt.Rd

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

2 changes: 2 additions & 0 deletions man/req_oauth_client_credentials.Rd

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

7 changes: 5 additions & 2 deletions man/req_oauth_device.Rd

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

7 changes: 5 additions & 2 deletions man/req_oauth_password.Rd

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

2 changes: 2 additions & 0 deletions man/req_oauth_refresh.Rd

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

7 changes: 7 additions & 0 deletions tests/testthat/test-oauth.R
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ test_that("can prune old files", {
cache_disk_prune(2, path)
expect_equal(dir(path), "a-token.rds")
})

# cache_path --------------------------------------------------------------

test_that("can override path with env var", {
withr::local_envvar("HTTR2_OAUTH_CACHE" = "/tmp")
expect_equal(oauth_cache_path(), "/tmp")
})
2 changes: 1 addition & 1 deletion tests/testthat/test-req-cache.R
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ test_that("handles responses with files", {
cache_set(req, resp)

# File should be copied in cache directory, and response body updated
body_path <- cache_path(req, ".body")
body_path <- req_cache_path(req, ".body")
expect_equal(readLines(body_path), "Hi there")
expect_equal(cache_get(req)$body, new_path(body_path))

Expand Down
Loading
Loading