Skip to content

Commit a6aac05

Browse files
authored
Improve redaction (#660)
* Always redact `Authorization()`. Fixes #649. * Support dynamic dots in `req_headers_redacted()` (#647) * Refactor code * Refactor tests and add a bunch more.
1 parent c249b90 commit a6aac05

File tree

8 files changed

+69
-23
lines changed

8 files changed

+69
-23
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# httr2 (development version)
22

3+
* `req_headers()` always redacts `Authorization` (#649).
4+
* `req_headers_redacted()` supports dynamic dots (#647)
35
* `resp_stream_sse()` now automatically retrieves the next event if the current event contains no data. The data is now returned as a single string (#650).
46
* `aws_v4_signature()` now works if url contains query parameters (@jeffreyzuber, #645).
57

R/req-auth.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ req_auth_basic <- function(req, username, password = NULL) {
3030
password <- check_password(password)
3131

3232
username_password <- openssl::base64_encode(paste0(username, ":", password))
33-
req_headers(req, Authorization = paste0("Basic ", username_password), .redact = "Authorization")
33+
req_headers(req, Authorization = paste0("Basic ", username_password))
3434
}
3535

3636
#' Authenticate request with bearer token
@@ -57,5 +57,5 @@ req_auth_basic <- function(req, username, password = NULL) {
5757
req_auth_bearer_token <- function(req, token) {
5858
check_request(req)
5959
check_string(token)
60-
req_headers(req, Authorization = paste("Bearer", token), .redact = "Authorization")
60+
req_headers(req, Authorization = paste("Bearer", token))
6161
}

R/req-headers.R

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
#' * Use `NULL` to reset a value to httr2's default
1515
#' * Use `""` to remove a header
1616
#' * Use a character vector to repeat a header.
17-
#' @param .redact Headers to redact. If `NULL`, the default, the added headers
18-
#' are not redacted.
17+
#' @param .redact A character vector of headers to redact. The Authorization
18+
#' header is always redacted.
1919
#' @returns A modified HTTP [request].
2020
#' @export
2121
#' @examples
@@ -63,16 +63,15 @@
6363
#' req_secret |> req_dry_run()
6464
req_headers <- function(.req, ..., .redact = NULL) {
6565
check_request(.req)
66-
67-
headers <- list2(...)
68-
header_names <- names2(headers)
6966
check_character(.redact, allow_null = TRUE)
7067

71-
redact_out <- attr(.req$headers, "redact") %||% .redact %||% character()
72-
redact_out <- union(redact_out, .redact)
73-
.req$headers <- modify_list(.req$headers, !!!headers)
68+
redact_prev <- attr(.req$headers, "redact")
69+
header_names <- names(list2(...))
70+
.req$headers <- modify_list(.req$headers, ...)
7471

75-
attr(.req$headers, "redact") <- redact_out
72+
.redact <- union(.redact, "Authorization")
73+
.redact <- .redact[tolower(.redact) %in% tolower(header_names)]
74+
attr(.req$headers, "redact") <- sort(union(.redact, redact_prev))
7675

7776
.req
7877
}
@@ -82,6 +81,6 @@ req_headers <- function(.req, ..., .redact = NULL) {
8281
req_headers_redacted <- function(.req, ...) {
8382
check_request(.req)
8483

85-
dots <- list(...)
86-
req_headers(.req, !!!dots, .redact = names(dots))
84+
headers <- list2(...)
85+
req_headers(.req, !!!headers, .redact = names(headers))
8786
}

man/req_headers.Rd

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/req-headers.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
# can control which headers to redact
1+
# is case insensitive
2+
3+
Code
4+
req
5+
Message
6+
<httr2_request>
7+
GET http://example.com
8+
Headers:
9+
* a: "<REDACTED>"
10+
Body: empty
11+
12+
# checks input types
213

314
Code
415
req_headers(req, a = 1L, b = 2L, .redact = 1L)

tests/testthat/helper.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
testthat::set_state_inspector(function() {
22
list(connections = getAllConnections())
33
})
4+
5+
expect_redacted <- function(req, expected) {
6+
expect_equal(attr(req$headers, "redact"), expected)
7+
}

tests/testthat/test-req-auth.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ test_that("can send username/password", {
33
password <- "p"
44
req1 <- request_test("/basic-auth/:user/:password")
55
req2 <- req1 %>% req_auth_basic(user, password)
6+
expect_redacted(req2, "Authorization")
67

78
expect_error(req_perform(req1), class = "httr2_http_401")
89
expect_error(req_perform(req2), NA)
910
})
1011

1112
test_that("can send bearer token", {
1213
req <- req_auth_bearer_token(request_test(), "abc")
14+
expect_redacted(req, "Authorization")
1315
expect_equal(req$headers, structure(list(Authorization = "Bearer abc"), redact = "Authorization"))
1416
})

tests/testthat/test-req-headers.R

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,46 @@ test_that("can add repeated headers", {
2020
expect_equal(resp$headers$a, c("a,b"))
2121
})
2222

23+
# redaction ---------------------------------------------------------------
24+
2325
test_that("can control which headers to redact", {
24-
expect_redact <- function(req, expected) {
25-
expect_equal(attr(req$headers, "redact"), expected)
26-
}
26+
req <- request("http://example.com")
27+
expect_redacted(req_headers(req, a = 1L, b = 2L), character())
28+
expect_redacted(req_headers(req, a = 1L, b = 2L, .redact = "a"), "a")
29+
expect_redacted(req_headers(req, a = 1L, b = 2L, .redact = c("a", "b")), c("a", "b"))
30+
})
31+
32+
test_that("only redacts supplied headers", {
33+
req <- request("http://example.com")
34+
expect_redacted(req_headers(req, a = 1L, b = 2L, .redact = "d"), character())
35+
})
36+
37+
test_that("redaction preserved across calls", {
38+
req <- request("http://example.com")
39+
req <- req_headers(req, a = 1L, .redact = "a")
40+
req <- req_headers(req, a = 2)
41+
expect_redacted(req, "a")
42+
})
43+
44+
test_that("req_headers_redacted redacts all headers", {
45+
req <- request("http://example.com")
46+
expect_redacted(req_headers_redacted(req, a = 1L, b = 2L), c("a", "b"))
47+
})
2748

49+
test_that("is case insensitive", {
2850
req <- request("http://example.com")
29-
expect_redact(req_headers(req, a = 1L, b = 2L), character())
30-
expect_redact(req_headers(req, a = 1L, b = 2L, .redact = c("a", "b")), c("a", "b"))
31-
expect_redact(req_headers(req, a = 1L, b = 2L, .redact = "a"), "a")
51+
req <- req_headers(req, a = 1L, .redact = "A")
52+
expect_redacted(req, "A")
53+
expect_snapshot(req)
54+
})
3255

33-
expect_redact(req_headers_redacted(req, a = 1L, b = 2L), c("a", "b"))
56+
test_that("authorization is always redacted", {
57+
req <- request("http://example.com")
58+
expect_redacted(req_headers(req, Authorization = "X"), "Authorization")
59+
})
3460

61+
test_that("checks input types", {
62+
req <- request("http://example.com")
3563
expect_snapshot(error = TRUE, {
3664
req_headers(req, a = 1L, b = 2L, .redact = 1L)
3765
})

0 commit comments

Comments
 (0)