diff --git a/R/req-auth-aws.R b/R/req-auth-aws.R index c040b834..ed92992e 100644 --- a/R/req-auth-aws.R +++ b/R/req-auth-aws.R @@ -1,9 +1,15 @@ - +#' Sign a request with the AWS SigV4 signing protocol +#' +#' This is a custom auth protocol implemented by AWS. +#' +#' @inheritParams req_perform +#' @param aws_access_key_id,aws_secret_access_key AWS key and secret. +#' @param aws_session_token AWS session token, if required. #' @param aws_service,aws_region The AWS service and region to use for the #' request. If not supplied, will be automatically parsed from the URL #' hostname. -#' @examples -#' creds <- paws.common::locate_credentials("bedrock") +#' @examplesIf httr2:::has_paws_credentials() +#' creds <- paws.common::locate_credentials() #' model_id <- "anthropic.claude-3-5-sonnet-20240620-v1:0" #' req <- request("https://bedrock-runtime.us-east-1.amazonaws.com") #' # https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html @@ -22,15 +28,12 @@ #' ) #' resp <- req_perform_connection(req) #' str(resp_body_json(resp)) -#' -# https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html req_auth_aws_v4 <- function(req, - aws_access_key_id, - aws_secret_access_key, - aws_session_token = NULL, - aws_service = NULL, - aws_region = NULL, - current_time = Sys.time()) { + aws_access_key_id, + aws_secret_access_key, + aws_session_token = NULL, + aws_service = NULL, + aws_region = NULL) { check_request(req) check_string(aws_access_key_id) @@ -38,9 +41,8 @@ req_auth_aws_v4 <- function(req, check_string(aws_session_token, allow_null = TRUE) check_string(aws_service, allow_null = TRUE) check_string(aws_region, allow_null = TRUE) - if (length(current_time) != 1 || !inherits(current_time, "POSIXct")) { - stop_input_type(current_time, "a single POSIXct") - } + + current_time <- Sys.time() body_sha256 <- openssl::sha256(req_body_get(req)) @@ -76,6 +78,7 @@ req_aws_headers <- function(req, current_time, aws_session_token, body_sha256) { ) } +# https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html aws_v4_signature <- function(method, url, headers, @@ -177,3 +180,15 @@ aws_v4_signature <- function(method, hmac_sha256 <- function(key, value) { openssl::sha256(charToRaw(value), key) } + +has_paws_credentials <- function() { + tryCatch( + { + paws.common::locate_credentials() + TRUE + }, + error = function(e) { + FALSE + } + ) +} diff --git a/man/req_auth_aws_v4.Rd b/man/req_auth_aws_v4.Rd new file mode 100644 index 00000000..32bc5580 --- /dev/null +++ b/man/req_auth_aws_v4.Rd @@ -0,0 +1,52 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/req-auth-aws.R +\name{req_auth_aws_v4} +\alias{req_auth_aws_v4} +\title{Sign a request with the AWS SigV4 signing protocol} +\usage{ +req_auth_aws_v4( + req, + aws_access_key_id, + aws_secret_access_key, + aws_session_token = NULL, + aws_service = NULL, + aws_region = NULL +) +} +\arguments{ +\item{req}{A httr2 \link{request} object.} + +\item{aws_access_key_id, aws_secret_access_key}{AWS key and secret.} + +\item{aws_session_token}{AWS session token, if required.} + +\item{aws_service, aws_region}{The AWS service and region to use for the +request. If not supplied, will be automatically parsed from the URL +hostname.} +} +\description{ +This is a custom auth protocol implemented by AWS. +} +\examples{ +\dontshow{if (httr2:::has_paws_credentials()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +creds <- paws.common::locate_credentials() +model_id <- "anthropic.claude-3-5-sonnet-20240620-v1:0" +req <- request("https://bedrock-runtime.us-east-1.amazonaws.com") +# https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html +req <- req_url_path_append(req, "model", model_id, "converse") +req <- req_body_json(req, list( + messages = list(list( + role = "user", + content = list(list(text = "What's your name?")) + )) +)) +req <- req_auth_aws_v4( + req, + aws_access_key_id = creds$access_key_id, + aws_secret_access_key = creds$secret_access_key, + aws_session_token = creds$session_token +) +resp <- req_perform_connection(req) +str(resp_body_json(resp)) +\dontshow{\}) # examplesIf} +} diff --git a/tests/testthat/_snaps/req-auth-aws.md b/tests/testthat/_snaps/req-auth-aws.md new file mode 100644 index 00000000..d0015609 --- /dev/null +++ b/tests/testthat/_snaps/req-auth-aws.md @@ -0,0 +1,28 @@ +# validates its inputs + + Code + req_auth_aws_v4(1) + Condition + Error in `req_auth_aws_v4()`: + ! `req` must be an HTTP request object, not the number 1. + Code + req_auth_aws_v4(req, 1) + Condition + Error in `req_auth_aws_v4()`: + ! `aws_access_key_id` must be a single string, not the number 1. + Code + req_auth_aws_v4(req, "", "", aws_session_token = 1) + Condition + Error in `req_auth_aws_v4()`: + ! `aws_session_token` must be a single string or `NULL`, not the number 1. + Code + req_auth_aws_v4(req, "", "", aws_service = 1) + Condition + Error in `req_auth_aws_v4()`: + ! `aws_service` must be a single string or `NULL`, not the number 1. + Code + req_auth_aws_v4(req, "", "", aws_region = 1) + Condition + Error in `req_auth_aws_v4()`: + ! `aws_region` must be a single string or `NULL`, not the number 1. + diff --git a/tests/testthat/test-req-auth-aws.R b/tests/testthat/test-req-auth-aws.R index 12420494..0352ebb7 100644 --- a/tests/testthat/test-req-auth-aws.R +++ b/tests/testthat/test-req-auth-aws.R @@ -1,8 +1,7 @@ test_that("can correctly sign a request", { - tryCatch( - creds <- paws.common::locate_credentials(), - error = function(cnd) skip("Can't locate AWS credentials") - ) + skip_if_not(has_paws_credentials()) + creds <- paws.common::locate_credentials() + # https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html req <- request("https://sts.amazonaws.com/") @@ -48,3 +47,14 @@ test_that("signing agrees with glacier example", { expect_equal(signature_pieces, known_signature) }) + +test_that("validates its inputs", { + req <- request("https://sts.amazonaws.com/") + expect_snapshot(error = TRUE, { + req_auth_aws_v4(1) + req_auth_aws_v4(req, 1) + req_auth_aws_v4(req, "", "", aws_session_token = 1) + req_auth_aws_v4(req, "", "", aws_service = 1) + req_auth_aws_v4(req, "", "", aws_region = 1) + }) +})