Skip to content

Commit

Permalink
Replace url with uri and allow request objects
Browse files Browse the repository at this point in the history
  • Loading branch information
alextwoods committed May 7, 2024
1 parent 1a81eab commit 8e6d659
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 91 deletions.
7 changes: 1 addition & 6 deletions gems/aws-sdk-core/lib/aws-sdk-core/signers/sigv4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,7 @@ def sign(request:, identity:, properties:)

apply_unsigned_body(request, properties)

signature = signer.sign_request(request: {
http_method: request.http_method,
url: request.uri,
headers: request.headers.to_h,
body: request.body
},
signature = signer.sign_request(request: request,
credentials: identity,
**properties)
apply_signature(request, signature)
Expand Down
102 changes: 59 additions & 43 deletions gems/aws-sigv4/lib/aws-sigv4/signer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ class Signer
def initialize(options = {})
@service = options[:service]
@region = options[:region]
@credentials = options[:credentials]
@credential_provider = options[:credential_provider]
@unsigned_headers = Set.new(options.fetch(:unsigned_headers, []))
# typical headers handled by proxies and load balancers
@unsigned_headers << 'expect'
Expand Down Expand Up @@ -156,14 +154,16 @@ def initialize(options = {})
# signature.metadata[:string_to_sign] #=> "..."
# signature.metadata[:content_sha256] #=> "..."
#
# @param [required, Hash] request A hash of request parts for signing.
# Parts must include :http_method and :url, and optionally include
# @param [required, Request, Hash] request A Request object such as
# Hearth::HTTP::Request or similar or a hash of request parts for
# signing.
# Parts must include :http_method and :uri, and optionally include
# :headers and :body.
#
# @option request [required, String] :http_method One of
# 'GET', 'HEAD', 'PUT', 'POST', 'PATCH', or 'DELETE'
#
# @option request [required, String, URI::HTTPS, URI::HTTP] :url
# @option request [required, String, URI::HTTPS, URI::HTTP] :uri
# The request URI. Must be a valid HTTP or HTTPS URI.
#
# @option request [Hash] :headers ({}) A hash of headers
Expand Down Expand Up @@ -193,15 +193,16 @@ def initialize(options = {})
def sign_request(request:, credentials:, **kwargs)
validate_credentials(credentials)
options = extract_options(kwargs)
request = extract_request(request)

if Signer.use_crt?
return crt_sign_request(request, credentials,
options)
end

http_method = extract_http_method(request)
url = extract_url(request)
Signer.normalize_path(url) if options[:normalize_path]
uri = extract_uri(request)
Signer.normalize_path(uri) if options[:normalize_path]
headers = downcase_headers(request[:headers])

datetime = headers['x-amz-date']
Expand All @@ -212,7 +213,7 @@ def sign_request(request:, credentials:, **kwargs)
content_sha256 ||= sha256_hexdigest(request[:body] || '')

sigv4_headers = {}
sigv4_headers['host'] = headers['host'] || host(url)
sigv4_headers['host'] = headers['host'] || host(uri)
sigv4_headers['x-amz-date'] = datetime
if credentials.session_token && !options[:omit_session_token]
if options[:signing_algorithm] == :'sigv4-s3express'
Expand All @@ -230,7 +231,7 @@ def sign_request(request:, credentials:, **kwargs)

# compute signature parts
creq = canonical_request(
http_method, url, options[:uri_escape_path],
http_method, uri, options[:uri_escape_path],
headers, options[:unsigned_headers], content_sha256
)

Expand Down Expand Up @@ -339,7 +340,7 @@ def sign_request(request:, credentials:, **kwargs)
# url = signer.presign_url(
# request: {
# http_method: 'GET',
# url: 'https://my-bucket.s3-us-east-1.amazonaws.com/key',
# : 'https://my-bucket.s3-us-east-1.amazonaws.com/key',
# },
# credentials: credentials
# )
Expand All @@ -350,7 +351,7 @@ def sign_request(request:, credentials:, **kwargs)
# url = signer.presign_url(
# request: {
# http_method: 'GET',
# url: 'https://my-bucket.s3-us-east-1.amazonaws.com/key',
# : 'https://my-bucket.s3-us-east-1.amazonaws.com/key',
# },
# credentials: credentials,
# expires_in: 3600 # one hour
Expand All @@ -364,22 +365,24 @@ def sign_request(request:, credentials:, **kwargs)
# url = signer.presign_url(
# request: {
# http_method: 'PUT',
# url: 'https://my-bucket.s3-us-east-1.amazonaws.com/key',
# uri: 'https://my-bucket.s3-us-east-1.amazonaws.com/key',
# headers: {
# 'X-Amz-Meta-Custom' => 'metadata'
# }
# },
# credentials: credentials
# )
#
# @param [required, Hash] request A hash of request parts for signing.
# Parts must include :http_method and :url, and optionally include
# @param [required, Request, Hash] request A Request object such as
# Hearth::HTTP::Request or similar or a hash of request parts for
# signing.
# Parts must include :http_method and :uri, and optionally include
# :headers and :body.
#
# @option request [required, String] :http_method One of
# 'GET', 'HEAD', 'PUT', 'POST', 'PATCH', or 'DELETE'
#
# @option request [required, String, URI::HTTPS, URI::HTTP] :url
# @option request [required, String, URI::HTTPS, URI::HTTP] :uri
# The request URI. Must be a valid HTTP or HTTPS URI.
#
# @option request [Hash] :headers ({}) A hash of headers that should be
Expand Down Expand Up @@ -423,15 +426,16 @@ def sign_request(request:, credentials:, **kwargs)
def presign_url(request:, credentials:, **kwargs)
validate_credentials(credentials)
options = extract_options(kwargs)
request = extract_request(request)

return crt_presign_url(request, credentials, options) if Signer.use_crt?

http_method = extract_http_method(request)
url = extract_url(request)
Signer.normalize_path(url) if options[:normalize_path]
uri = extract_uri(request)
Signer.normalize_path(uri) if options[:normalize_path]

headers = downcase_headers(request[:headers])
headers['host'] ||= host(url)
headers['host'] ||= host(uri)

datetime = headers['x-amz-date']
datetime ||= options[:time].utc.strftime('%Y%m%dT%H%M%SZ')
Expand Down Expand Up @@ -469,14 +473,14 @@ def presign_url(request:, credentials:, **kwargs)
"#{Signer.uri_escape(key)}=#{Signer.uri_escape(value)}"
end.join('&')

if url.query
url.query += "&#{params}"
if uri.query
uri.query += "&#{params}"
else
url.query = params
uri.query = params
end

creq = canonical_request(
http_method, url, options[:uri_escape_path], headers,
http_method, uri, options[:uri_escape_path], headers,
options[:unsigned_headers], content_sha256
)
sts = string_to_sign(
Expand All @@ -486,14 +490,14 @@ def presign_url(request:, credentials:, **kwargs)
credentials.secret_access_key, date, options[:region],
options[:service], sts
)
url.query += "&X-Amz-Signature=#{sig}"
uri.query += "&X-Amz-Signature=#{sig}"
if credentials.session_token && options[:omit_session_token]
escaped_token = CGI.escape(credentials.session_token)
url.query += "&X-Amz-Security-Token=#{escaped_token}"
uri.query += "&X-Amz-Security-Token=#{escaped_token}"
end

PresignedUrl.new(
url: url,
url: uri,
headers: sigv4_headers,
metadata: {
signature: sig,
Expand All @@ -506,12 +510,12 @@ def presign_url(request:, credentials:, **kwargs)

private

def canonical_request(http_method, url, uri_escape_path, headers,
def canonical_request(http_method, uri, uri_escape_path, headers,
unsigned_headers, content_sha256)
[
http_method,
path(url, uri_escape_path),
normalized_querystring(url.query || ''),
path(uri, uri_escape_path),
normalized_querystring(uri.query || ''),
"#{canonical_headers(headers, unsigned_headers)}\n",
signed_headers(headers, unsigned_headers),
content_sha256
Expand Down Expand Up @@ -590,8 +594,8 @@ def signature(secret_access_key, date, region, service, string_to_sign)
# hmac(k_credentials, string_to_sign)
# end

def path(url, uri_escape_path)
path = url.path
def path(uri, uri_escape_path)
path = uri.path
path = '/' if path == ''
if uri_escape_path
Signer.uri_escape_path(path)
Expand Down Expand Up @@ -726,6 +730,18 @@ def extract_options(kwargs)
}
end

# allow for either a Request object or hash
def extract_request(request)
return request if request.is_a?(Hash)

{
http_method: request.http_method,
uri: request.uri,
headers: request.headers.to_h,
body: request.body
}
end

def extract_service(kwargs)
if kwargs[:service]
kwargs[:service]
Expand Down Expand Up @@ -788,10 +804,10 @@ def extract_http_method(request)
request[:http_method].upcase
end

def extract_url(request)
raise ArgumentError, 'missing required option :url' unless request[:url]
def extract_uri(request)
raise ArgumentError, 'missing required option :uri' unless request[:uri]

URI(request[:url])
URI(request[:uri])
end

def extract_expires_in(kwargs)
Expand Down Expand Up @@ -834,7 +850,7 @@ def crt_sign_request(request, credentials, options)
crt_creds = crt_credentials(credentials)

http_method = extract_http_method(request)
url = extract_url(request)
uri = extract_uri(request)
headers = downcase_headers(request[:headers])

datetime =
Expand All @@ -847,7 +863,7 @@ def crt_sign_request(request, credentials, options)
content_sha256 ||= sha256_hexdigest(request[:body] || '')

sigv4_headers = {}
sigv4_headers['host'] = headers['host'] || host(url)
sigv4_headers['host'] = headers['host'] || host(uri)

# Modify the user-agent to add usage of crt-signer
if headers.include?('user-agent')
Expand Down Expand Up @@ -882,7 +898,7 @@ def crt_sign_request(request, credentials, options)
omit_session_token: options[:omit_session_token]
)
http_request = Aws::Crt::Http::Message.new(
http_method, url.to_s, headers
http_method, uri.to_s, headers
)
signable = Aws::Crt::Auth::Signable.new(http_request)

Expand All @@ -905,9 +921,9 @@ def crt_presign_url(request, credentials, options)
crt_creds = crt_credentials(credentials)

http_method = extract_http_method(request)
url = extract_url(request)
uri = extract_uri(request)
headers = downcase_headers(request[:headers])
headers['host'] ||= host(url)
headers['host'] ||= host(uri)

datetime =
if headers.include?('x-amz-date')
Expand All @@ -926,7 +942,7 @@ def crt_presign_url(request, credentials, options)
service: options[:service],
date: datetime,
signed_body_value: content_sha256,
signed_body_header_type: :sbht_none, # url does not use checksum
signed_body_header_type: :sbht_none, # uri does not use checksum
credentials: crt_creds,
unsigned_headers: options[:unsigned_headers],
use_double_uri_encode: options[:uri_escape_path],
Expand All @@ -937,20 +953,20 @@ def crt_presign_url(request, credentials, options)
)
)
http_request = Aws::Crt::Http::Message.new(
http_method, url.to_s, headers
http_method, uri.to_s, headers
)
signable = Aws::Crt::Auth::Signable.new(http_request)

signing_result = Aws::Crt::Auth::Signer.sign_request(
config, signable, http_method, url.to_s
config, signable, http_method, uri.to_s
)
url = URI.parse(signing_result[:path])
uri = URI.parse(signing_result[:path])

# Headers that should be used with the URL
sigv4_headers = (headers.to_a - signing_result[:headers].to_a).to_h

PresignedUrl.new(
url: url,
url: uri,
headers: sigv4_headers,
metadata: {
signature: 'CRT_INTERNAL',
Expand Down
Loading

0 comments on commit 8e6d659

Please sign in to comment.