Class: RightScale::CloudApi::AWS::S3::RequestSigner
- Inherits:
-
CloudApi::Routine
- Object
- CloudApi::Routine
- RightScale::CloudApi::AWS::S3::RequestSigner
- Defined in:
- lib/cloud/aws/s3/routines/request_signer.rb
Overview
S3 Request signer
Direct Known Subclasses
Defined Under Namespace
Classes: Error
Constant Summary collapse
- SUB_RESOURCES =
This guys are used to sign a request
%w{ acl cors delete lifecycle location logging notification policy requestPayment tagging torrent uploads versionId versioning versions website }
- OVERRIDE_RESPONSE_HEADERS =
Using Query String API Amazon allows to override some of response headers:
response-content-type response-content-language response-expires reponse-cache-control response-content-disposition response-content-encoding
/^response-/- ONE_YEAR_OF_SECONDS =
One year in seconds
365*60*60*24
Instance Method Summary collapse
-
#compute_body(body, content_type) ⇒ Object
Returns response body.
-
#compute_bucket_name_and_object_path(bucket, relative_path) ⇒ Array
Extracts S3 bucket name and escapes relative path.
-
#compute_canonicalized_bucket(bucket) ⇒ String
Returns canonicalized bucket.
-
#compute_canonicalized_path(bucket, relative_path, params) ⇒ String
Returns canonicalized path.
-
#compute_headers!(headers, body, host) ⇒ Hash
Sets response headers.
-
#compute_host(bucket, uri) ⇒ URI
Figure out if we need to add bucket name into the host name.
-
#compute_path(bucket, object, params) ⇒ String
Builds request path.
-
#compute_signature(access_key, secret_key, verb, bucket, object, params, headers) ⇒ String
Computes signature.
-
#get_subresources(params) ⇒ Hash
Returns a list of sub-resource(s).
-
#process ⇒ void
Authenticates an S3 request.
Instance Method Details
#compute_body(body, content_type) ⇒ Object
Returns response body
223 224 225 226 227 228 |
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 223 def compute_body(body, content_type) return body if body._blank? # Make sure it is a String instance return body unless body.is_a?(Hash) Utils::contentify_body(body, content_type) end |
#compute_bucket_name_and_object_path(bucket, relative_path) ⇒ Array
Extracts S3 bucket name and escapes relative path
177 178 179 180 181 182 183 184 |
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 177 def compute_bucket_name_and_object_path(bucket, relative_path) return [bucket, relative_path] if bucket # This is a very first attempt: relative_path.to_s[/^([^\/]*)\/?(.*)$/] # Escape part of the path that may have UTF-8 chars (in S3 Object name for instance). # Amazon wants them to be escaped before we sign the request. [ $1, Utils::AWS::amz_escape($2) ] end |
#compute_canonicalized_bucket(bucket) ⇒ String
Returns canonicalized bucket
138 139 140 141 |
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 138 def compute_canonicalized_bucket(bucket) bucket += '/' if Utils::AWS::is_dns_bucket?(bucket) bucket end |
#compute_canonicalized_path(bucket, relative_path, params) ⇒ String
Returns canonicalized path
157 158 159 160 161 162 163 |
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 157 def compute_canonicalized_path(bucket, relative_path, params) can_bucket = compute_canonicalized_bucket(bucket) sub_params = get_subresources(params) # We use the block below to avoid escaping: Amazon does not like escaped bucket and '/' # in canonicalized path (relative path has been escaped above already) Utils::join_urn(can_bucket, relative_path, sub_params) { |value| value } end |
#compute_headers!(headers, body, host) ⇒ Hash
Sets response headers
239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 239 def compute_headers!(headers, body, host) # Make sure 'content-type' is set. # P.S. Ruby 2.1+ sets 'content-type' by default for POST and PUT requests. # So we need to include it into our signature to avoid the error below: # 'The request signature we calculated does not match the signature you provided. # Check your key and signing method.' headers.set_if_blank('content-type', 'application/octet-stream') headers.set_if_blank('date', Time::now.utc.httpdate) headers['content-md5'] = Base64::encode64(Digest::MD5::digest(body)).strip if !body._blank? headers['host'] = host headers end |
#compute_host(bucket, uri) ⇒ URI
Figure out if we need to add bucket name into the host name
If there was a redirect and it had ‘location’ header then there is nothing to do with the host, otherwise we have to add the bucket to the host.
P.S. When Amazon returns a redirect (usually 301) with the new host in the message body, the new host does not have the bucket name in it. But if it is 307 and the host is in the location header then that host name already includes the bucket in it. The only thing we can do so far is to check if the host name starts with the bucket and the name is at least 4th level DNS name.
Examples:
* my-bucket.s3-ap-southeast-2.amazonaws.com
* my-bucket.s3.amazonaws.com
* s3.amazonaws.com
208 209 210 211 212 213 |
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 208 def compute_host(bucket, uri) return uri unless Utils::AWS::is_dns_bucket?(bucket) return uri if uri.host[/^#{bucket}\..+\.[^.]+\.[^.]+$/] uri.host = "#{bucket}.#{uri.host}" uri end |
#compute_path(bucket, object, params) ⇒ String
Builds request path
278 279 280 281 282 283 284 |
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 278 def compute_path(bucket, object, params) data = [] data << bucket unless Utils::AWS::is_dns_bucket?(bucket) data << object data << params Utils::join_urn(*data) end |
#compute_signature(access_key, secret_key, verb, bucket, object, params, headers) ⇒ String
Computes signature
264 265 266 267 |
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 264 def compute_signature(access_key, secret_key, verb, bucket, object, params, headers) can_path = compute_canonicalized_path(bucket, object, params) Utils::AWS::sign_s3_signature(secret_key, verb, can_path, headers) end |
#get_subresources(params) ⇒ Hash
Returns a list of sub-resource(s)
Sub-resources are acl, torrent, versioning, location, etc. See SUB_RESOURCES
114 115 116 117 118 119 120 121 |
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 114 def get_subresources(params) result = {} params.each do |key, value| next unless SUB_RESOURCES.include?(key) || key[OVERRIDE_RESPONSE_HEADERS] result[key] = (value._blank? ? nil : value) end result end |
#process ⇒ void
This method returns an undefined value.
Authenticates an S3 request
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 77 def process uri = @data[:connection][:uri] access_key = @data[:credentials][:aws_access_key_id] secret_key = @data[:credentials][:aws_secret_access_key] body = @data[:request][:body] bucket = @data[:request][:bucket] headers = @data[:request][:headers] object = @data[:request][:relative_path] params = @data[:request][:params] verb = @data[:request][:verb] bucket, object = compute_bucket_name_and_object_path(bucket, object) body = compute_body(body, headers['content-type']) uri = compute_host(bucket, uri) compute_headers!(headers, body, uri.host) # Set Authorization header signature = compute_signature(access_key, secret_key, verb, bucket, object, params, headers) headers['authorization'] = "AWS #{access_key}:#{signature}" @data[:request][:body] = body @data[:request][:bucket] = bucket @data[:request][:headers] = headers @data[:request][:params] = params @data[:request][:path] = compute_path(bucket, object, params) @data[:request][:relative_path] = object @data[:connection][:uri] = uri end |