right_aws_api 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5329a8cc0261eacfd9bf681412e9064185934f7e
4
- data.tar.gz: f6d75ece8c71660a0e6c465827083c1e655ca385
3
+ metadata.gz: 164b81b2c4e7845571d6616ce59b22a3baa7788a
4
+ data.tar.gz: e7cbd22f3857d650a026b29699e22cc9718d1821
5
5
  SHA512:
6
- metadata.gz: 4b6aa4f90078247eae455daf5e90d71323ea0dcf74f224885b4fb89f4beac77da3c7448f9e849e5253ed6d8019c7d97afa58164916f3ed9038143acb74ab129c
7
- data.tar.gz: fddd2af29aa08ce0f364b69a45fb177f08799c6f28dc20ef03c47329ee8273e881bad92dad36bc42b8f6cc7c05246fbc80fecb65adb12b8573e313acc78c67cc
6
+ metadata.gz: 4b970d6a9794c29953c16e63018f7a97522476f66a7357ea5be7e0bd6d3d110b880615d86531a45a70b7ebe41d96d3bf18697074d2a7fc5844b1fc16caf51089
7
+ data.tar.gz: d0846816165f048ebfa43d026d144c03c35158f4006c750a7d0c14117a1f50d4031a3d46422b34321935d182e074f121bc426e84be043b3ba76371ed03b59ffa
data/HISTORY CHANGED
@@ -1,2 +1,11 @@
1
+ == 2015-01-16
2
+ - v0.2.2
3
+ Added an ability to not use DNS like buckets in AWS S3
4
+ Some minor bugs were fixed in singature V4
5
+
6
+ == 2014-10-30
7
+ - v0.2.1
8
+ Added support for AWS v4 signature and make it the default
9
+
1
10
  == 2013-06-28
2
11
  - pre-release candidate created
@@ -171,7 +171,7 @@ module RightScale
171
171
  #
172
172
  # @example
173
173
  # sign_s3_signature('secret', :get, 'xxx/yyy/zzz/object', {'header'=>'value'}) #=>
174
- # "i85igH0sftHD/cGZcLiBKcYEuks="
174
+ # "i85igH0sftHD/cGZcLiBKcYEuks="
175
175
  #
176
176
  # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html
177
177
  #
@@ -196,7 +196,210 @@ module RightScale
196
196
  sign(aws_secret_access_key, string_to_sign)
197
197
  end
198
198
 
199
-
199
+
200
+ def self.sign_v4_get_service_and_region(host)
201
+ result =
202
+ case
203
+ when host[ /^iam\.amazonaws\.com$/i ] then ['iam', 'us-east-1']
204
+ when host[ /^(.*\.)?s3\.amazonaws\.com$/i ] then ['s3', 'us-east-1']
205
+ when host[ /^(.*\.)?s3-external-1\.amazonaws\.com$/i ] then ['s3', 'us-east-1']
206
+ when host[ /s3-website-([^.]+)\.amazonaws\.com$/i ] then ['s3', $1]
207
+ when host[ /^(.*\.)?s3-([^.]+).amazonaws\.com$/i ] then ['s3', $2]
208
+ when host[ /^(.*\.)?s3\.([^.]+)\.amazonaws\.com$/i ] then ['s3', $2]
209
+ else host[ /^([^.]+)\.([^.]+)\.amazonaws\.com$/i ] && [$1, $2]
210
+ end
211
+ fail(ArgumentError, "Cannot extract service name from %s host" % host.inspect) if !result || result[0].to_s.empty?
212
+ fail(ArgumentError, "Cannot extract region name from %s host" % host.inspect) if result[1].to_s.empty?
213
+ result
214
+ end
215
+
216
+
217
+ # Signs and Authenticates REST Requests
218
+ #
219
+ # @param [String] aws_secret_access_key
220
+ # @param [String] aws_access_key
221
+ # @param [String] host
222
+ # @param [Hash] request
223
+ #
224
+ # @return [String]
225
+ #
226
+ # @see http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
227
+ #
228
+ def self.sign_v4_signature(aws_access_key, aws_secret_access_key, host, request, method=:headers)
229
+ now = Time.now.utc
230
+ current_date = now.strftime("%Y%m%d")
231
+ current_time = now.strftime("%Y%m%dT%H%M%SZ")
232
+ host = host.downcase
233
+ service, region = sign_v4_get_service_and_region(host)
234
+ creds_scope = "%s/%s/%s/aws4_request" % [current_date, region, service]
235
+ algorithm = "AWS4-HMAC-SHA256"
236
+
237
+ # Verb
238
+ canonical_verb = sign_v4_get_canonical_verb(request[:verb])
239
+
240
+ # Path
241
+ request[:path] ||= '/'
242
+ canonical_path = sign_v4_get_canonical_path(request[:path])
243
+
244
+ # Headers (Auth)
245
+ request[:headers].delete('Authorization')
246
+ if method == :headers
247
+ canonical_payload = sign_v4_headers(request, host, current_time)
248
+ end
249
+ # Headers (Standard)
250
+ request[:headers]['Host'] = host
251
+ _headers = {}
252
+ request[:headers].each do |key, value|
253
+ _headers[key.to_s.downcase] = value.is_a?(Array) ? value.join(',') : value
254
+ end
255
+ canonical_headers = sign_v4_get_canonical_headers(_headers)
256
+ signed_headers = sign_v4_get_signed_headers(_headers)
257
+
258
+ # Params (Auth)
259
+ if method != :headers
260
+ canonical_payload = sign_v4_query_params(
261
+ request,
262
+ algorithm,
263
+ current_time,
264
+ signed_headers,
265
+ aws_access_key,
266
+ creds_scope
267
+ )
268
+ end
269
+ # Params (Standard)
270
+ canonical_query_string = Utils::params_to_urn(request[:params]){ |value| amz_escape(value) }
271
+
272
+ # Canonical String
273
+ canonical_string = sign_v4_get_canonical_string(
274
+ canonical_verb,
275
+ canonical_path,
276
+ canonical_query_string,
277
+ canonical_headers,
278
+ signed_headers,
279
+ canonical_payload
280
+ )
281
+
282
+ # StringToSign
283
+ string_to_sign = sign_v4_get_string_to_sign(algorithm, current_time, creds_scope, canonical_string)
284
+
285
+ # Signature
286
+ signature = sign_v4_get_signature_key(aws_secret_access_key, string_to_sign, current_date, region, service)
287
+
288
+ request[:path] += "?%s" % canonical_query_string unless canonical_query_string.empty?
289
+
290
+ if method == :headers
291
+ # Authorization Header
292
+ authorization_header = "%s Credential=%s/%s, SignedHeaders=%s, Signature=%s" %
293
+ [algorithm, aws_access_key, creds_scope, signed_headers, signature]
294
+ request[:headers]['Authorization'] = authorization_header
295
+ else
296
+ request[:path] += "&X-Amz-Signature=%s" % signature
297
+ end
298
+ end
299
+
300
+
301
+ def self.sign_v4_get_canonical_verb(verb)
302
+ verb.to_s.upcase
303
+ end
304
+
305
+
306
+ def self.sign_v4_get_canonical_path(path)
307
+ path
308
+ end
309
+
310
+
311
+ def self.sign_v4_query_params(request, algorithm, current_time, signed_headers, aws_access_key, creds_scope)
312
+ expires_at = request[:params]['X-Amz-Expires'] || 3600
313
+ expires_at = expires_at.to_i if expires_at.is_a?(Time)
314
+
315
+ request[:params]['X-Amz-Date'] = current_time
316
+ request[:params]['X-Amz-Expires'] = expires_at
317
+ request[:params]['X-Amz-Algorithm'] = algorithm
318
+ request[:params]['X-Amz-SignedHeaders'] = signed_headers
319
+ request[:params]['X-Amz-Credential'] = "%s/%s" % [aws_access_key, creds_scope]
320
+
321
+ 'UNSIGNED-PAYLOAD'
322
+ end
323
+
324
+
325
+ def self.sign_v4_get_canonical_headers(headers)
326
+ headers.sort.map{ |key, value| "#{key}:#{value}" }.join("\n")
327
+ end
328
+
329
+
330
+ def self.sign_v4_get_signed_headers(headers)
331
+ headers.keys.sort.join(';')
332
+ end
333
+
334
+
335
+ def self.sign_v4_headers(request, host, current_time)
336
+ expires_at = request[:headers]['X-Amz-Expires'].first || 3600
337
+ expires_at = expires_at.to_i if expires_at.is_a?(Time)
338
+
339
+ if request[:body].is_a?(IO)
340
+ canonical_payload = ''
341
+ request[:headers].set_if_blank('X-Amz-Content-Sha256', 'UNSIGNED-PAYLOAD')
342
+ else
343
+ request[:body] = request[:body].to_s
344
+ canonical_payload = hex_encode(Digest::SHA256.digest(request[:body]))
345
+ content_type = 'application/x-www-form-urlencoded; charset=utf-8'
346
+ content_md5 = Base64::encode64(Digest::MD5::digest(request[:body])).strip
347
+ request[:headers].set_if_blank('Content-Length', request[:body].bytesize)
348
+ request[:headers].set_if_blank('Content-Type', content_type)
349
+ request[:headers].set_if_blank('Content-Md5', content_md5)
350
+ request[:headers].set_if_blank('X-Amz-Content-Sha256', canonical_payload)
351
+ end
352
+ request[:headers]['X-Amz-Date'] = current_time
353
+ request[:headers]['X-Amz-Expires'] = expires_at
354
+
355
+ canonical_payload
356
+ end
357
+
358
+
359
+ # Signature V4: Returns Canonical String
360
+ #
361
+ # @return [String]
362
+ #
363
+ def self.sign_v4_get_canonical_string(verb, path, query_string, headers, signed_headers, payload)
364
+ verb + "\n" +
365
+ path + "\n" +
366
+ query_string + "\n" +
367
+ headers + "\n\n" +
368
+ signed_headers + "\n" +
369
+ payload
370
+ end
371
+
372
+
373
+ # Signature V4: A string to sign value
374
+ #
375
+ # @return [String]
376
+ #
377
+ def self.sign_v4_get_string_to_sign(algorithm, current_time, creds_scope, canonical_string)
378
+ algorithm + "\n" +
379
+ current_time + "\n" +
380
+ creds_scope + "\n" +
381
+ hex_encode(Digest::SHA256.digest(canonical_string)).downcase
382
+ end
383
+
384
+
385
+ #Helpers from AWS documentation http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html
386
+ def self.sign_v4_get_signature_key(key, string_to_sign, date, region, service, digest = @@digest256)
387
+ k_date = OpenSSL::HMAC.digest(digest, "AWS4" + key, date)
388
+ k_region = OpenSSL::HMAC.digest(digest, k_date, region)
389
+ k_service = OpenSSL::HMAC.digest(digest, k_region, service)
390
+ k_signing = OpenSSL::HMAC.digest(digest, k_service, "aws4_request")
391
+ hex_encode OpenSSL::HMAC.digest(digest, k_signing, string_to_sign)
392
+ end
393
+
394
+
395
+ def self.hex_encode(bindata)
396
+ result=""
397
+ data=bindata.unpack("C*")
398
+ data.each {|b| result+= "%02x" % b}
399
+ result
400
+ end
401
+
402
+
200
403
  # Parametrizes data to the format that Amazon EC2 (and compatible APIs) loves
201
404
  #
202
405
  # @param [Hash] data
@@ -207,7 +410,7 @@ module RightScale
207
410
  # # Where hash is:
208
411
  # { Name.?.Mask => Value | [ Values ],
209
412
  # NamePrefix.? => [{ SubNameA.1 => ValueA.1, SubNameB.1 => ValueB.1 }, # any simple parameter
210
- # ...,
413
+ # ...,
211
414
  # { SubNameN.X => ValueN.X, SubNameM.X => ValueN.X }] # see BlockDeviceMapping case
212
415
  #
213
416
  # @example
@@ -237,15 +440,15 @@ module RightScale
237
440
  # 'MaxCount' => 2,
238
441
  # 'KeyName' => 'my-key',
239
442
  # 'SecurityGroupId' => ['sg-01234567', 'sg-12345670', 'sg-23456701'],
240
- # 'BlockDeviceMapping' => [
241
- # { 'DeviceName' => '/dev/sda1',
242
- # 'Ebs.SnapshotId' => 'snap-01234567',
443
+ # 'BlockDeviceMapping' => [
444
+ # { 'DeviceName' => '/dev/sda1',
445
+ # 'Ebs.SnapshotId' => 'snap-01234567',
243
446
  # 'Ebs.VolumeSize' => 20,
244
447
  # 'Ebs.DeleteOnTermination' => true },
245
- # { 'DeviceName' => '/dev/sdb1',
246
- # 'Ebs.SnapshotId' => 'snap-12345670',
448
+ # { 'DeviceName' => '/dev/sdb1',
449
+ # 'Ebs.SnapshotId' => 'snap-12345670',
247
450
  # 'Ebs.VolumeSize' => 10,
248
- # 'Ebs.DeleteOnTermination' => false } ] ) #=>
451
+ # 'Ebs.DeleteOnTermination' => false } ] ) #=>
249
452
  # {
250
453
  # "BlockDeviceMapping.1.DeviceName" => "/dev/sda1",
251
454
  # "BlockDeviceMapping.1.Ebs.DeleteOnTermination" => true,
@@ -262,7 +465,7 @@ module RightScale
262
465
  # "SecurityGroupId.1" => "sg-01234567",
263
466
  # "SecurityGroupId.2" => "sg-12345670",
264
467
  # "SecurityGroupId.3" => "sg-23456701"
265
- # }
468
+ # }
266
469
  #
267
470
  # @example
268
471
  # parametrize( 'DomainName' => 'kdclient',
@@ -276,7 +479,7 @@ module RightScale
276
479
  # { 'ItemName' => 'diana',
277
480
  # 'Attribute' => [ { 'Name' => 'sex', 'Value' => 'female' },
278
481
  # { 'Name' => 'weight', 'Value' => '120'},
279
- # { 'Name' => 'age', 'Value' => '25'} ] } ] ) #=>
482
+ # { 'Name' => 'age', 'Value' => '25'} ] } ] ) #=>
280
483
  # { "DomainName" => "kdclient",
281
484
  # "Item.1.ItemName" => "konstantin",
282
485
  # "Item.1.Attribute.1.Name" => "sex",
@@ -321,8 +524,8 @@ module RightScale
321
524
  end
322
525
  result
323
526
  end
324
-
527
+
325
528
  end
326
529
  end
327
530
  end
328
- end
531
+ end
@@ -116,7 +116,7 @@ module RightScale
116
116
  opts[:headers] = params.delete(:headers) || {}
117
117
  opts[:options] = params.delete(:options) || {}
118
118
  opts[:params] = parametrize(params)
119
- process_api_request(:get, '', opts, &block)
119
+ process_api_request(:post, '', opts, &block)
120
120
  end
121
121
 
122
122
 
@@ -43,38 +43,22 @@ module RightScale
43
43
  # no example
44
44
  #
45
45
  def process
46
- # Make sure all the required params are set
47
- @data[:request][:params]['AWSAccessKeyId'] = @data[:credentials][:aws_access_key_id]
48
- @data[:request][:params]['Version'] ||= @data[:options][:api_version]
49
46
  # Compile a final request path
50
47
  @data[:request][:path] = Utils::join_urn(@data[:connection][:uri].path, @data[:request][:relative_path])
51
- # Sign the request
52
- sign_proc = Proc::new do |data|
53
- Utils::AWS::sign_v2_signature( data[:credentials][:aws_secret_access_key],
54
- data[:request][:params] || {},
55
- data[:request][:verb],
56
- data[:connection][:uri].host,
57
- data[:request][:path] )
58
- end
59
- signed_path = sign_proc.call(@data)
60
- # Rebuild the request as POST if its path is too long
61
- if signed_path.size > MAX_GET_REQUEST_PATH_LENGTH && @data[:request][:verb] == :get
62
- @data[:request][:verb] = :post
63
- signed_path = sign_proc.call(@data)
64
- end
65
- # Set new path or body and content-type
66
- case @data[:request][:verb]
67
- when :get
68
- @data[:request][:path] << "?#{signed_path}"
69
- when :post
70
- @data[:request][:body] = signed_path
71
- @data[:request][:headers]['content-type'] = 'application/x-www-form-urlencoded; charset=utf-8'
72
- else
73
- fail Error::new("Unsupported HTTP verb: #{@data[:request][:verb]}")
74
- end
48
+
49
+ # Swap query params and body
50
+ @data[:request][:params]['Version'] ||= @data[:options][:api_version]
51
+ @data[:request][:body] = Utils::params_to_urn(@data[:request][:params]){ |value| Utils::AWS::amz_escape(value) }
52
+ @data[:request][:params] = {}
53
+
54
+ Utils::AWS::sign_v4_signature(
55
+ @data[:credentials][:aws_access_key_id],
56
+ @data[:credentials][:aws_secret_access_key],
57
+ @data[:connection][:uri].host,
58
+ @data[:request]
59
+ )
75
60
  end
76
61
  end
77
-
78
62
  end
79
63
  end
80
- end
64
+ end
@@ -43,25 +43,24 @@ module RightScale
43
43
  fail Error::new("Body must be blank") unless @data[:request][:body]._blank?
44
44
  fail Error::new("Headers must be blank") unless @data[:request][:headers]._blank?
45
45
 
46
- uri = @data[:connection][:uri]
47
- access_key = @data[:credentials][:aws_access_key_id]
48
- secret_key = @data[:credentials][:aws_secret_access_key]
49
- bucket = @data[:request][:bucket]
50
- object = @data[:request][:relative_path]
51
- params = @data[:request][:params]
52
- verb = @data[:request][:verb]
53
-
46
+ uri = @data[:connection][:uri]
47
+ bucket = @data[:request][:bucket]
48
+ object = @data[:request][:relative_path]
54
49
  bucket, object = compute_bucket_name_and_object_path(bucket, object)
55
50
  uri = compute_host(bucket, uri)
56
51
 
57
- compute_params!(params, access_key)
52
+ @data[:request][:path] = compute_path(bucket, object)
58
53
 
59
- # Set Auth param
60
- signature = compute_signature(secret_key, verb, bucket, object, params)
61
- params['Signature'] = signature
54
+ Utils::AWS::sign_v4_signature(
55
+ @data[:credentials][:aws_access_key_id],
56
+ @data[:credentials][:aws_secret_access_key],
57
+ @data[:connection][:uri].host,
58
+ @data[:request],
59
+ :query_params
60
+ )
62
61
 
63
62
  # Compute href
64
- path = compute_path(bucket, object, params)
63
+ path = @data[:request][:path]
65
64
  uri.path, uri.query = path.split('?')
66
65
  @data[:result] = uri.to_s
67
66
 
@@ -69,37 +68,6 @@ module RightScale
69
68
  @data[:vars][:system][:done] = true
70
69
  end
71
70
 
72
-
73
- # Sets response params
74
- #
75
- # @param [Hash] params
76
- #
77
- # @return [Hash]
78
- #
79
- def compute_params!(params, access_key)
80
- # Expires
81
- expires = params['Expires']
82
- expires ||= Time.now.utc.to_i + ONE_YEAR_OF_SECONDS
83
- expires = expires.to_i unless expires.is_a?(Fixnum)
84
- params['Expires'] = expires
85
- params['AWSAccessKeyId'] = access_key
86
- params
87
- end
88
-
89
-
90
- # Computes signature
91
- #
92
- # @param [String] secret_key
93
- # @param [String] verb
94
- # @param [String] bucket
95
- # @param [Hash] params
96
- #
97
- # @return [String]
98
- #
99
- def compute_signature(secret_key, verb, bucket, object, params)
100
- can_path = compute_canonicalized_path(bucket, object, params)
101
- Utils::AWS::sign_s3_signature(secret_key, verb, can_path, { 'expires' => params['Expires'] })
102
- end
103
71
  end
104
72
 
105
73
  end
@@ -293,6 +293,19 @@ module RightScale
293
293
  # 'https://s3.amazonaws.com/?AWSAccessKeyId=AK...TA&Expires=1436651780&
294
294
  # Signature=XK...53s%3D'
295
295
  #
296
+ # @example
297
+ # link = RightScale::CloudApi::AWS::S3::Link::Manager::new(key, secret, endpoint)
298
+ # link.GetObject('Bucket' => 'foo', 'Object' => 'bar') #=>
299
+ # 'https://foo.s3.amazonaws.com/bar?AWSAccessKeyId=AK...TA&Expires=1436557118&
300
+ # Signature=hg...%3D&response-content-type=image%2Fpeg'
301
+ #
302
+ # @example
303
+ # # Do not use DNS-like bucket hosts but put buckets into path
304
+ # link = RightScale::CloudApi::AWS::S3::Link::Manager::new(key, secret, endpoint, :no_dns_buckets => true)
305
+ # link.GetObject('Bucket' => 'foo', 'Object' => 'bar') #=>
306
+ # 'https://s3.amazonaws.com/foo/bar?AWSAccessKeyId=AK...TA&Expires=1436557118&
307
+ # Signature=hg...%3D&response-content-type=image%2Fpeg'
308
+ #
296
309
  # @see ApiManager
297
310
  # @see Wrapper::DEFAULT.extended Wrapper::DEFAULT.extended (click [View source])
298
311
  # @see http://docs.aws.amazon.com/AmazonS3/latest/API/APIRest.html
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  #--
2
3
  # Copyright (c) 2013 RightScale, Inc.
3
4
  #
@@ -75,91 +76,28 @@ module RightScale
75
76
  # # no example
76
77
  #
77
78
  def process
78
- uri = @data[:connection][:uri]
79
- access_key = @data[:credentials][:aws_access_key_id]
80
- secret_key = @data[:credentials][:aws_secret_access_key]
81
- body = @data[:request][:body]
82
- bucket = @data[:request][:bucket]
83
- headers = @data[:request][:headers]
84
- object = @data[:request][:relative_path]
85
- params = @data[:request][:params]
86
- verb = @data[:request][:verb]
87
-
79
+ uri = @data[:connection][:uri]
80
+ body = @data[:request][:body]
81
+ bucket = @data[:request][:bucket]
82
+ object = @data[:request][:relative_path]
88
83
  bucket, object = compute_bucket_name_and_object_path(bucket, object)
89
- body = compute_body(body, headers['content-type'])
84
+ body = compute_body(body, @data[:request][:headers]['content-type'])
90
85
  uri = compute_host(bucket, uri)
91
86
 
92
- compute_headers!(headers, body, uri.host)
93
-
94
- # Set Authorization header
95
- signature = compute_signature(access_key, secret_key, verb, bucket, object, params, headers)
96
- headers['authorization'] = "AWS #{access_key}:#{signature}"
97
-
98
- @data[:request][:body] = body
99
- @data[:request][:bucket] = bucket
100
- @data[:request][:headers] = headers
101
- @data[:request][:params] = params
102
- @data[:request][:path] = compute_path(bucket, object, params)
103
- @data[:request][:relative_path] = object
104
- @data[:connection][:uri] = uri
105
- end
106
-
107
-
108
- # Returns a list of sub-resource(s)
109
- #
110
- # Sub-resources are acl, torrent, versioning, location, etc. See SUB_RESOURCES
111
- #
112
- # @return [Hash]
113
- #
114
- def get_subresources(params)
115
- result = {}
116
- params.each do |key, value|
117
- next unless SUB_RESOURCES.include?(key) || key[OVERRIDE_RESPONSE_HEADERS]
118
- result[key] = (value._blank? ? nil : value)
119
- end
120
- result
121
- end
87
+ compute_headers!(@data[:request][:headers], body, uri.host)
122
88
 
89
+ @data[:connection][:uri] = uri
90
+ @data[:request][:bucket] = bucket
91
+ @data[:request][:relative_path] = object
92
+ @data[:request][:body] = body
93
+ @data[:request][:path] = compute_path(bucket, object)
123
94
 
124
- # Returns canonicalized bucket
125
- #
126
- # @param [String] bucket
127
- #
128
- # @return [String]
129
- #
130
- # @example
131
- # # DNS bucket
132
- # compute_canonicalized_bucket('foo-bar') #=> 'foo-bar/'
133
- #
134
- # @example
135
- # # non DNS bucket
136
- # compute_canonicalized_bucket('foo_bar') #=> 'foo_bar'
137
- #
138
- def compute_canonicalized_bucket(bucket)
139
- bucket += '/' if Utils::AWS::is_dns_bucket?(bucket)
140
- bucket
141
- end
142
-
143
-
144
- # Returns canonicalized path
145
- #
146
- # @param [String] bucket
147
- # @param [String] relative_path
148
- # @param [Hash] params
149
- #
150
- # @return [String]
151
- #
152
- # @example
153
- # params = { 'Foo' => 1, 'acl' => '2', 'response-content-type' => 'jpg' }
154
- # compute_canonicalized_path('foo-bar_bucket', 'a/b/c/d.jpg', params)
155
- # #=> '/foo-bar_bucket/a/b/c/d.jpg?acl=3&response-content-type=jpg'
156
- #
157
- def compute_canonicalized_path(bucket, relative_path, params)
158
- can_bucket = compute_canonicalized_bucket(bucket)
159
- sub_params = get_subresources(params)
160
- # We use the block below to avoid escaping: Amazon does not like escaped bucket and '/'
161
- # in canonicalized path (relative path has been escaped above already)
162
- Utils::join_urn(can_bucket, relative_path, sub_params) { |value| value }
95
+ Utils::AWS::sign_v4_signature(
96
+ @data[:credentials][:aws_access_key_id],
97
+ @data[:credentials][:aws_secret_access_key],
98
+ @data[:connection][:uri].host,
99
+ @data[:request]
100
+ )
163
101
  end
164
102
 
165
103
 
@@ -180,7 +118,10 @@ module RightScale
180
118
  relative_path.to_s[/^([^\/]*)\/?(.*)$/]
181
119
  # Escape part of the path that may have UTF-8 chars (in S3 Object name for instance).
182
120
  # Amazon wants them to be escaped before we sign the request.
183
- [ $1, Utils::AWS::amz_escape($2) ]
121
+ # P.S. but do not escape "/" (signature v4 does not like this)
122
+ #
123
+ object = $2.to_s.split('/').map{|i| Utils::AWS::amz_escape(i)}.join('/')
124
+ [ $1, object ]
184
125
  end
185
126
 
186
127
 
@@ -206,7 +147,7 @@ module RightScale
206
147
  # @return [URI]
207
148
  #
208
149
  def compute_host(bucket, uri)
209
- return uri unless Utils::AWS::is_dns_bucket?(bucket)
150
+ return uri unless is_dns_bucket?(bucket)
210
151
  return uri if uri.host[/^#{bucket}\..+\.[^.]+\.[^.]+$/]
211
152
  uri.host = "#{bucket}.#{uri.host}"
212
153
  uri
@@ -221,7 +162,7 @@ module RightScale
221
162
  # @return [Object]
222
163
  #
223
164
  def compute_body(body, content_type)
224
- return body if body._blank?
165
+ return body if body.nil?
225
166
  # Make sure it is a String instance
226
167
  return body unless body.is_a?(Hash)
227
168
  Utils::contentify_body(body, content_type)
@@ -243,30 +184,10 @@ module RightScale
243
184
  # 'The request signature we calculated does not match the signature you provided.
244
185
  # Check your key and signing method.'
245
186
  headers.set_if_blank('content-type', 'application/octet-stream')
246
- headers.set_if_blank('date', Time::now.utc.httpdate)
247
- headers['content-md5'] = Base64::encode64(Digest::MD5::digest(body)).strip if !body._blank?
248
- headers['host'] = host
249
187
  headers
250
188
  end
251
189
 
252
190
 
253
- # Computes signature
254
- #
255
- # @param [String] access_key
256
- # @param [String] secret_key
257
- # @param [String] verb
258
- # @param [String] bucket
259
- # @param [Hash] params
260
- # @param [Hash] headers
261
- #
262
- # @return [String]
263
- #
264
- def compute_signature(access_key, secret_key, verb, bucket, object, params, headers)
265
- can_path = compute_canonicalized_path(bucket, object, params)
266
- Utils::AWS::sign_s3_signature(secret_key, verb, can_path, headers)
267
- end
268
-
269
-
270
191
  # Builds request path
271
192
  #
272
193
  # @param [String] bucket
@@ -275,16 +196,26 @@ module RightScale
275
196
  #
276
197
  # @return [String]
277
198
  #
278
- def compute_path(bucket, object, params)
199
+ def compute_path(bucket, object)
279
200
  data = []
280
- data << bucket unless Utils::AWS::is_dns_bucket?(bucket)
201
+ data << bucket unless is_dns_bucket?(bucket)
281
202
  data << object
282
- data << params
283
203
  Utils::join_urn(*data)
284
204
  end
285
205
 
206
+ # Returns +true+ if DNS compatible buckets are enabled (default) and the
207
+ # given bucket is DNS compatible
208
+ #
209
+ # @param [String] bucket
210
+ # @return [Boolean]
211
+ #
212
+ def is_dns_bucket?(bucket)
213
+ return false if @data[:options][:cloud][:no_dns_buckets]
214
+ Utils::AWS::is_dns_bucket?(bucket)
215
+ end
216
+
286
217
  end
287
218
  end
288
219
  end
289
220
  end
290
- end
221
+ end