Class: RightScale::CloudApi::AWS::S3::RequestSigner

Inherits:
CloudApi::Routine
  • Object
show all
Defined in:
lib/cloud/aws/s3/routines/request_signer.rb

Overview

S3 Request signer

Direct Known Subclasses

Link::RequestSigner

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

Instance Method Details

#compute_body(body, content_type) ⇒ Object

Returns response body

Parameters:

  • body (Object)
  • content_type (String)

Returns:

  • (Object)


168
169
170
171
172
173
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 168

def compute_body(body, content_type)
  return body if body.nil?
  # 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

Examples:

subject.compute_bucket_name_and_object_path(nil, 'my-test-bucket/foo/bar/банана.jpg') #=>
  ['my-test-bucket', 'foo%2Fbar%2F%D0%B1%D0%B0%D0%BD%D0%B0%D0%BD%D0%B0.jpg']

Parameters:

  • bucket (String)
  • relative_path (String)

Returns:

  • (Array)
    bucket, escaped_relative_path


115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 115

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.
  # P.S. but do not escape "/" (signature v4 does not like this)
  #
  new_bucket = $1
  pre_object = $2.to_s
  object     = pre_object.split('/').map{|i| Utils::AWS::amz_escape(i)}.join('/')
  # Preserve '/' if it is the last symbol of the object because it is an operation on a folder
  object    += '/' if object.length > 0 && pre_object[/\/$/]
  [ new_bucket, object ]
end

#compute_headers!(headers, body, host) ⇒ Hash

Sets response headers

Parameters:

  • headers (Hash)
  • body (String)
  • host (String)

Returns:

  • (Hash)


184
185
186
187
188
189
190
191
192
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 184

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', 'binary/octet-stream')
  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

Parameters:

  • bucket (String)
  • uri (URI)

Returns:

  • (URI)


153
154
155
156
157
158
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 153

def compute_host(bucket, uri)
  return uri unless is_dns_bucket?(bucket)
  return uri if uri.host[/^#{bucket}\..+\.[^.]+\.[^.]+$/]
  uri.host = "#{bucket}.#{uri.host}"
  uri
end

#compute_path(bucket, object) ⇒ String

Builds request path

Parameters:

  • bucket (String)
  • object (String)

Returns:

  • (String)


202
203
204
205
206
207
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 202

def compute_path(bucket, object)
  data = []
  data << bucket unless is_dns_bucket?(bucket)
  data << object
  Utils::join_urn(*data)
end

#is_dns_bucket?(bucket) ⇒ Boolean

Returns true if DNS compatible buckets are enabled (default) and the given bucket is DNS compatible

Parameters:

  • bucket (String)

Returns:

  • (Boolean)


215
216
217
218
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 215

def is_dns_bucket?(bucket)
  return false if no_dns_buckets?
  Utils::AWS::is_dns_bucket?(bucket)
end

#no_dns_buckets?Boolean

Returns:

  • (Boolean)


221
222
223
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 221

def no_dns_buckets?
  !!@data[:options][:cloud][:no_dns_buckets]
end

#processvoid

This method returns an undefined value.

Authenticates an S3 request

Examples:

# no example


78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/cloud/aws/s3/routines/request_signer.rb', line 78

def process
  uri            = @data[:connection][:uri]
  body           = @data[:request][:body]
  bucket         = @data[:request][:bucket]
  object         = @data[:request][:relative_path]
  bucket, object = compute_bucket_name_and_object_path(bucket, object)
  body           = compute_body(body, @data[:request][:headers]['content-type'])
  uri            = compute_host(bucket, uri)

  compute_headers!(@data[:request][:headers], body, uri.host)

  @data[:connection][:uri]        = uri
  @data[:request][:bucket]        = bucket
  @data[:request][:relative_path] = object
  @data[:request][:body]          = body
  @data[:request][:path]          = compute_path(bucket, object)

  Utils::AWS::sign_v4_signature(
    @data[:credentials][:aws_access_key_id],
    @data[:credentials][:aws_secret_access_key],
    @data[:connection][:uri].host,
    @data[:request]
  )
end