right_aws_api 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cloud/aws/s3/link/manager.rb +70 -0
- data/lib/cloud/aws/s3/link/routines/request_signer.rb +109 -0
- data/lib/cloud/aws/s3/link/wrappers/default.rb +40 -0
- data/lib/cloud/aws/s3/manager.rb +16 -18
- data/lib/cloud/aws/s3/routines/request_signer.rb +206 -159
- data/lib/right_aws_api.rb +1 -0
- data/lib/right_aws_api_version.rb +1 -1
- data/right_aws_api.gemspec +1 -0
- data/spec/cloud/aws/s3/link/routines/request_signer_spec.rb +53 -0
- data/spec/cloud/aws/s3/routines/request_signer_spec.rb +230 -0
- metadata +23 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5329a8cc0261eacfd9bf681412e9064185934f7e
|
4
|
+
data.tar.gz: f6d75ece8c71660a0e6c465827083c1e655ca385
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b6aa4f90078247eae455daf5e90d71323ea0dcf74f224885b4fb89f4beac77da3c7448f9e849e5253ed6d8019c7d97afa58164916f3ed9038143acb74ab129c
|
7
|
+
data.tar.gz: fddd2af29aa08ce0f364b69a45fb177f08799c6f28dc20ef03c47329ee8273e881bad92dad36bc42b8f6cc7c05246fbc80fecb65adb12b8573e313acc78c67cc
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2014 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require "cloud/aws/s3/manager"
|
25
|
+
require "cloud/aws/s3/link/routines/request_signer"
|
26
|
+
require "cloud/aws/s3/link/wrappers/default"
|
27
|
+
|
28
|
+
module RightScale
|
29
|
+
module CloudApi
|
30
|
+
module AWS
|
31
|
+
module S3
|
32
|
+
|
33
|
+
# Simple Storage Service Query API link namespace
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
#
|
37
|
+
module Link
|
38
|
+
|
39
|
+
# S3 Query API links manager
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# link = RightScale::CloudApi::AWS::S3::Link::Manager::new(key, secret, endpoint)
|
43
|
+
# link.get(
|
44
|
+
# 'devs-us-east/kd/Константин',
|
45
|
+
# :params => { 'response-content-type' => 'image/peg'}
|
46
|
+
# ) #=>
|
47
|
+
# https://devs-us-east.s3.amazonaws.com/kd%2F%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D0%B0%
|
48
|
+
# D0%BD%D1%82%D0%B8%D0%BD?AWSAccessKeyId=AK...TA&Expires=1436557118&
|
49
|
+
# Signature=hg...%3D&response-content-type=image%2Fpeg
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# link.ListAllMyBuckets#=>
|
53
|
+
# https://s3.amazonaws.com/?AWSAccessKeyId=AK...TA&Expires=1436651780&
|
54
|
+
# Signature=XK...53s%3D
|
55
|
+
#
|
56
|
+
class Manager < S3::Manager
|
57
|
+
end
|
58
|
+
|
59
|
+
class ApiManager < S3::ApiManager
|
60
|
+
set_routine CloudApi::RequestInitializer
|
61
|
+
set_routine AWS::S3::Link::RequestSigner
|
62
|
+
|
63
|
+
include Mixin::QueryApiPatterns
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
module RightScale
|
25
|
+
module CloudApi
|
26
|
+
module AWS
|
27
|
+
module S3
|
28
|
+
module Link
|
29
|
+
|
30
|
+
# S3 Request signer
|
31
|
+
class RequestSigner < S3::RequestSigner
|
32
|
+
|
33
|
+
|
34
|
+
# Authenticates an S3 request
|
35
|
+
#
|
36
|
+
# @return [void]
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# # no example
|
40
|
+
#
|
41
|
+
def process
|
42
|
+
fail Error::new("Only GET method is supported") unless @data[:request][:verb] == :get
|
43
|
+
fail Error::new("Body must be blank") unless @data[:request][:body]._blank?
|
44
|
+
fail Error::new("Headers must be blank") unless @data[:request][:headers]._blank?
|
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
|
+
|
54
|
+
bucket, object = compute_bucket_name_and_object_path(bucket, object)
|
55
|
+
uri = compute_host(bucket, uri)
|
56
|
+
|
57
|
+
compute_params!(params, access_key)
|
58
|
+
|
59
|
+
# Set Auth param
|
60
|
+
signature = compute_signature(secret_key, verb, bucket, object, params)
|
61
|
+
params['Signature'] = signature
|
62
|
+
|
63
|
+
# Compute href
|
64
|
+
path = compute_path(bucket, object, params)
|
65
|
+
uri.path, uri.query = path.split('?')
|
66
|
+
@data[:result] = uri.to_s
|
67
|
+
|
68
|
+
# Set completion flag
|
69
|
+
@data[:vars][:system][:done] = true
|
70
|
+
end
|
71
|
+
|
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
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2014 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require "cloud/aws/s3/wrappers/default"
|
25
|
+
|
26
|
+
module RightScale
|
27
|
+
module CloudApi
|
28
|
+
module AWS
|
29
|
+
module S3
|
30
|
+
module Link
|
31
|
+
# S3 Link Wrapper namespace
|
32
|
+
module Wrapper
|
33
|
+
# Default wrapper
|
34
|
+
DEFAULT = S3::Wrapper::DEFAULT
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/cloud/aws/s3/manager.rb
CHANGED
@@ -45,7 +45,7 @@ module RightScale
|
|
45
45
|
#
|
46
46
|
# @example
|
47
47
|
# # -- Using HTTP verb methods --
|
48
|
-
#
|
48
|
+
#
|
49
49
|
# # List all buckets
|
50
50
|
# s3.get
|
51
51
|
#
|
@@ -142,7 +142,7 @@ module RightScale
|
|
142
142
|
# :headers => {'content-type' => 'image/jpeg'})
|
143
143
|
#
|
144
144
|
# @example
|
145
|
-
#
|
145
|
+
#
|
146
146
|
# # List all buckets
|
147
147
|
# s3.ListAllMyBuckets #=>
|
148
148
|
# {"ListAllMyBucketsResult"=>
|
@@ -179,7 +179,7 @@ module RightScale
|
|
179
179
|
# "Size"=>"3257230",
|
180
180
|
# "Key"=>"kd/boot.jpg"},
|
181
181
|
# "@xmlns"=>"http://s3.amazonaws.com/doc/2006-03-01/",
|
182
|
-
# "Prefix"=>"kd"}}
|
182
|
+
# "Prefix"=>"kd"}}
|
183
183
|
#
|
184
184
|
#
|
185
185
|
# @example
|
@@ -279,21 +279,19 @@ module RightScale
|
|
279
279
|
#
|
280
280
|
#
|
281
281
|
# @example
|
282
|
-
#
|
283
|
-
#
|
284
|
-
#
|
285
|
-
#
|
286
|
-
#
|
287
|
-
#
|
288
|
-
#
|
289
|
-
#
|
290
|
-
# s3.with_options(:cloud=>{:link => true}) do
|
291
|
-
# pp s3.GetObject({'Bucket'=>'my-bucket', 'Object'=>'kd/kd0.test', 'versionId'=>"00eYZeb291o4"}, opts)
|
292
|
-
# pp s3.GetObject({'Bucket'=>'my-bucket', 'Object'=>'kd/kd1.test', 'versionId'=>"fafaf1obp1W4"}, opts)
|
293
|
-
# pp s3.GetObject({'Bucket'=>'my-bucket', 'Object'=>'kd/kd2.test', 'versionId'=>"00asdTebp1W4"}, opts)
|
294
|
-
# pp s3.GetObject({'Bucket'=>'my-bucket', 'Object'=>'kd/kd3.test', 'versionId'=>"0lkjreobp1W4"}, opts)
|
295
|
-
# end
|
282
|
+
# link = RightScale::CloudApi::AWS::S3::Link::Manager::new(key, secret, endpoint)
|
283
|
+
# link.get(
|
284
|
+
# 'devs-us-east/kd/Константин',
|
285
|
+
# :params => { 'response-content-type' => 'image/peg'}
|
286
|
+
# ) #=>
|
287
|
+
# 'https://devs-us-east.s3.amazonaws.com/kd%2F%D0%9A%D0%BE%D0%BD%D1%81%D1%82%D0%B0%
|
288
|
+
# D0%BD%D1%82%D0%B8%D0%BD?AWSAccessKeyId=AK...TA&Expires=1436557118&
|
289
|
+
# Signature=hg...%3D&response-content-type=image%2Fpeg'
|
296
290
|
#
|
291
|
+
# @example
|
292
|
+
# link.ListAllMyBuckets #=>
|
293
|
+
# 'https://s3.amazonaws.com/?AWSAccessKeyId=AK...TA&Expires=1436651780&
|
294
|
+
# Signature=XK...53s%3D'
|
297
295
|
#
|
298
296
|
# @see ApiManager
|
299
297
|
# @see Wrapper::DEFAULT.extended Wrapper::DEFAULT.extended (click [View source])
|
@@ -365,8 +363,8 @@ module RightScale
|
|
365
363
|
raise Error::new("Opts must be Hash not #{opts.class.name}") unless opts.is_a?(Hash)
|
366
364
|
process_api_request(verb, relative_path, opts, &block)
|
367
365
|
end
|
368
|
-
|
369
366
|
end
|
367
|
+
|
370
368
|
end
|
371
369
|
end
|
372
370
|
end
|
@@ -55,17 +55,18 @@ module RightScale
|
|
55
55
|
|
56
56
|
|
57
57
|
# Using Query String API Amazon allows to override some of response headers:
|
58
|
-
#
|
58
|
+
#
|
59
59
|
# response-content-type response-content-language response-expires
|
60
60
|
# reponse-cache-control response-content-disposition response-content-encoding
|
61
|
-
#
|
61
|
+
#
|
62
62
|
# @see http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGET.html?r=2145
|
63
63
|
#
|
64
|
-
OVERRIDE_RESPONSE_HEADERS = /^response-/
|
64
|
+
OVERRIDE_RESPONSE_HEADERS = /^response-/
|
65
65
|
|
66
66
|
# One year in seconds
|
67
67
|
ONE_YEAR_OF_SECONDS = 365*60*60*24
|
68
|
-
|
68
|
+
|
69
|
+
|
69
70
|
# Authenticates an S3 request
|
70
71
|
#
|
71
72
|
# @return [void]
|
@@ -74,169 +75,215 @@ module RightScale
|
|
74
75
|
# # no example
|
75
76
|
#
|
76
77
|
def process
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
@data[:request][:
|
81
|
-
|
82
|
-
|
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
|
+
|
88
|
+
bucket, object = compute_bucket_name_and_object_path(bucket, object)
|
89
|
+
body = compute_body(body, headers['content-type'])
|
90
|
+
uri = compute_host(bucket, uri)
|
91
|
+
|
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
|
83
106
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
static_path = "#{static_path}/" unless static_path[/\/$/]
|
97
|
-
# Store the path: we may need it for signing redirects later.
|
98
|
-
@data[:request][:static_path] = static_path
|
99
|
-
else
|
100
|
-
# This is a retry or a redirect:
|
101
|
-
# 1. Extract the bucket name from the request data;
|
102
|
-
# 2. Get rid of the path the remote server sent in the location header. We are
|
103
|
-
# re-signing the request and have to build everything from the scratch.
|
104
|
-
# In the crazy case when the new location has path differs from the original one
|
105
|
-
# we are screwed up and we will get "SignatureDoesNotMatch" error. But this does
|
106
|
-
# not seem to be the case for Amazon or Euca.
|
107
|
-
bucket_name = @data[:request][:bucket]
|
108
|
-
# Revert static path back to the original value.
|
109
|
-
static_path = @data[:request][:static_path]
|
110
|
-
@data[:connection][:uri].path = static_path
|
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)
|
111
119
|
end
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
|
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
|
+
|
112
143
|
|
113
|
-
|
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 }
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
# Extracts S3 bucket name and escapes relative path
|
167
|
+
#
|
168
|
+
# @param [String] bucket
|
169
|
+
# @param [String] relative_path
|
170
|
+
#
|
171
|
+
# @return [Array] [bucket, escaped_relative_path]
|
172
|
+
#
|
173
|
+
# @example
|
174
|
+
# subject.compute_bucket_name_and_object_path(nil, 'my-test-bucket/foo/bar/банана.jpg') #=>
|
175
|
+
# ['my-test-bucket', 'foo%2Fbar%2F%D0%B1%D0%B0%D0%BD%D0%B0%D0%BD%D0%B0.jpg']
|
176
|
+
#
|
177
|
+
def compute_bucket_name_and_object_path(bucket, relative_path)
|
178
|
+
return [bucket, relative_path] if bucket
|
179
|
+
# This is a very first attempt:
|
180
|
+
relative_path.to_s[/^([^\/]*)\/?(.*)$/]
|
181
|
+
# Escape part of the path that may have UTF-8 chars (in S3 Object name for instance).
|
114
182
|
# Amazon wants them to be escaped before we sign the request.
|
115
|
-
|
116
|
-
|
117
|
-
@data[:request][:relative_path] = Utils::AWS::amz_escape(@data[:request][:relative_path])
|
118
|
-
|
119
|
-
# Calculate a canonical path (bucket part must end with '/')
|
120
|
-
bucket_string = Utils::AWS::is_dns_bucket?(bucket_name) ? "#{bucket_name}/" : bucket_name.to_s
|
121
|
-
canonicalized_path = Utils::join_urn(static_path,
|
122
|
-
bucket_string,
|
123
|
-
@data[:request][:relative_path],
|
124
|
-
sub_resources ){ |value| value } # pass this block to avoid escaping: Amazon does not like escaped things in canonicalized_path
|
125
|
-
|
126
|
-
# Make sure headers required for authentication are set
|
127
|
-
unless @data[:options][:cloud][:link]
|
128
|
-
# Make sure 'content-type' is set.
|
129
|
-
# P.S. Ruby 2.1+ sets 'content-type' by default for POST and PUT requests.
|
130
|
-
# So we need to include it into our signature to avoid the error below:
|
131
|
-
# 'The request signature we calculated does not match the signature you provided.
|
132
|
-
# Check your key and signing method.'
|
133
|
-
@data[:request][:headers].set_if_blank('content-type', 'application/octet-stream')
|
134
|
-
# REST Auth:
|
135
|
-
unless @data[:request][:body]._blank?
|
136
|
-
# Fix body if it is a Hash instance
|
137
|
-
if @data[:request][:body].is_a?(Hash)
|
138
|
-
@data[:request][:body] = Utils::contentify_body(@data[:request][:body], @data[:request][:headers]['content-type'])
|
139
|
-
end
|
140
|
-
# Calculate 'content-md5' when possible (some API calls wanna have it set)
|
141
|
-
if @data[:request][:body].is_a?(String)
|
142
|
-
@data[:request][:headers]['content-md5'] = Base64::encode64(Digest::MD5::digest(@data[:request][:body])).strip
|
143
|
-
end
|
144
|
-
end
|
145
|
-
# Set date
|
146
|
-
@data[:request][:headers].set_if_blank('date', Time::now.utc.httpdate)
|
147
|
-
# Sign a request
|
148
|
-
signature = Utils::AWS::sign_s3_signature( @data[:credentials][:aws_secret_access_key],
|
149
|
-
@data[:request][:verb],
|
150
|
-
canonicalized_path,
|
151
|
-
@data[:request][:headers])
|
152
|
-
@data[:request][:headers]['authorization'] = "AWS #{@data[:credentials][:aws_access_key_id]}:#{signature}"
|
153
|
-
else
|
154
|
-
# @see http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
|
155
|
-
#
|
156
|
-
# Amazon: ... We assume that when a browser makes the GET request, it won't provide a Content-MD5 or a Content-Type header,
|
157
|
-
# nor will it set any x-amz- headers, so those parts of the StringToSign are left blank. ...
|
158
|
-
#
|
159
|
-
# Only GET requests!
|
160
|
-
raise Error::new("Only GET requests are supported by S3 Query String API") unless @data[:request][:verb] == :get
|
161
|
-
# Expires
|
162
|
-
expires = Utils::dearrayify(@data[:request][:headers]['expires'].first || (Time.now.utc.to_i + ONE_YEAR_OF_SECONDS))
|
163
|
-
expires = expires.to_i unless expires.is_a?(Fixnum)
|
164
|
-
# QUERY STRING AUTH: ('expires' and 'x-amz-*' headers are not supported)
|
165
|
-
@data[:request][:params]['Expires'] = expires
|
166
|
-
@data[:request][:headers]['expires'] = expires # a hack to sign a record
|
167
|
-
@data[:request][:headers].dup.each do |header, values|
|
168
|
-
@data[:request][:headers].delete(header) unless header.to_s[/(^x-amz-)|(^expires$)/]
|
169
|
-
end
|
170
|
-
@data[:request][:params]['AWSAccessKeyId'] = @data[:credentials][:aws_access_key_id]
|
171
|
-
# Sign a request
|
172
|
-
signature = Utils::AWS::sign_s3_signature( @data[:credentials][:aws_secret_access_key],
|
173
|
-
@data[:request][:verb],
|
174
|
-
canonicalized_path,
|
175
|
-
@data[:request][:headers] )
|
176
|
-
@data[:request][:params]['Signature'] = signature
|
177
|
-
# we dont need this header any more
|
178
|
-
@data[:request][:headers].delete('expires')
|
179
|
-
end
|
183
|
+
[ $1, Utils::AWS::amz_escape($2) ]
|
184
|
+
end
|
180
185
|
|
181
|
-
# Sub-domain compatible buckets vs incompatible ones
|
182
|
-
if !@data[:options][:cloud][:no_subdomains] && Utils::AWS::is_dns_bucket?(bucket_name)
|
183
|
-
# DNS compatible bucket name:
|
184
|
-
#
|
185
|
-
# Figure out if we need to add bucket name into the host name. It is rediculous but
|
186
|
-
# sometimes Amazon returns a redirect to a host with the bucket name already mixed in
|
187
|
-
# but sometimes without.
|
188
|
-
# The only thing we can do so far is to check if the host name starts with the bucket
|
189
|
-
# and the name is at least 4th level DNS name.
|
190
|
-
#
|
191
|
-
# Examples:
|
192
|
-
# * my-bucket.s3-ap-southeast-2.amazonaws.com
|
193
|
-
# * my-bucket.s3.amazonaws.com
|
194
|
-
# * s3.amazonaws.com
|
195
|
-
#
|
196
|
-
# P.S. This assumtion will not work for any other providers but we will figure it out later
|
197
|
-
# if we support any. The only other provider we support is Eucalyptus but it always
|
198
|
-
# expects that the bucket goes into path and never into the host therefore we are
|
199
|
-
# OK with Euca (Euca is expected to be run with :no_subdomains => true).
|
200
|
-
#
|
201
|
-
unless @data[:connection][:uri].host[/^#{bucket_name}\..+\.[^.]+\.[^.]+$/]
|
202
|
-
# If there was a redirect and it had 'location' header then there is nothing to do with the host
|
203
|
-
# otherwise we have to add the bucket to the host.
|
204
|
-
# P.S. When Amazon returns a redirect (usually 301) with the new host in the message body
|
205
|
-
# the new host does not have the bucket name in it. But if it is 307 and the host is in the location
|
206
|
-
# header then that host name already includes the bucket in it. Grrrr....
|
207
|
-
@data[:connection][:uri].host = "#{bucket_name}.#{@data[:connection][:uri].host}"
|
208
|
-
end
|
209
|
-
@data[:request][:path] = Utils::join_urn( @data[:connection][:uri].path,
|
210
|
-
@data[:request][:relative_path],
|
211
|
-
@data[:request][:params] )
|
212
|
-
else
|
213
|
-
# Old incompatible or Eucalyptus
|
214
|
-
@data[:request][:path] = Utils::join_urn( @data[:connection][:uri].path,
|
215
|
-
"#{bucket_name}",
|
216
|
-
@data[:request][:relative_path],
|
217
|
-
@data[:request][:params] )
|
218
|
-
end
|
219
186
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
187
|
+
# Figure out if we need to add bucket name into the host name
|
188
|
+
#
|
189
|
+
# If there was a redirect and it had 'location' header then there is nothing to do
|
190
|
+
# with the host, otherwise we have to add the bucket to the host.
|
191
|
+
#
|
192
|
+
# P.S. When Amazon returns a redirect (usually 301) with the new host in the message
|
193
|
+
# body, the new host does not have the bucket name in it. But if it is 307 and the
|
194
|
+
# host is in the location header then that host name already includes the bucket in it.
|
195
|
+
# The only thing we can do so far is to check if the host name starts with the bucket
|
196
|
+
# and the name is at least 4th level DNS name.
|
197
|
+
#
|
198
|
+
# Examples:
|
199
|
+
# * my-bucket.s3-ap-southeast-2.amazonaws.com
|
200
|
+
# * my-bucket.s3.amazonaws.com
|
201
|
+
# * s3.amazonaws.com
|
202
|
+
#
|
203
|
+
# @param [String] bucket
|
204
|
+
# @param [URI] uri
|
205
|
+
#
|
206
|
+
# @return [URI]
|
207
|
+
#
|
208
|
+
def compute_host(bucket, uri)
|
209
|
+
return uri unless Utils::AWS::is_dns_bucket?(bucket)
|
210
|
+
return uri if uri.host[/^#{bucket}\..+\.[^.]+\.[^.]+$/]
|
211
|
+
uri.host = "#{bucket}.#{uri.host}"
|
212
|
+
uri
|
237
213
|
end
|
238
|
-
end
|
239
214
|
|
215
|
+
|
216
|
+
# Returns response body
|
217
|
+
#
|
218
|
+
# @param [Object] body
|
219
|
+
# @param [String] content_type
|
220
|
+
#
|
221
|
+
# @return [Object]
|
222
|
+
#
|
223
|
+
def compute_body(body, content_type)
|
224
|
+
return body if body._blank?
|
225
|
+
# Make sure it is a String instance
|
226
|
+
return body unless body.is_a?(Hash)
|
227
|
+
Utils::contentify_body(body, content_type)
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
# Sets response headers
|
232
|
+
#
|
233
|
+
# @param [Hash] headers
|
234
|
+
# @param [String] body
|
235
|
+
# @param [String] host
|
236
|
+
#
|
237
|
+
# @return [Hash]
|
238
|
+
#
|
239
|
+
def compute_headers!(headers, body, host)
|
240
|
+
# Make sure 'content-type' is set.
|
241
|
+
# P.S. Ruby 2.1+ sets 'content-type' by default for POST and PUT requests.
|
242
|
+
# So we need to include it into our signature to avoid the error below:
|
243
|
+
# 'The request signature we calculated does not match the signature you provided.
|
244
|
+
# Check your key and signing method.'
|
245
|
+
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
|
+
headers
|
250
|
+
end
|
251
|
+
|
252
|
+
|
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
|
+
# Builds request path
|
271
|
+
#
|
272
|
+
# @param [String] bucket
|
273
|
+
# @param [String] object
|
274
|
+
# @param [Hash] params
|
275
|
+
#
|
276
|
+
# @return [String]
|
277
|
+
#
|
278
|
+
def compute_path(bucket, object, params)
|
279
|
+
data = []
|
280
|
+
data << bucket unless Utils::AWS::is_dns_bucket?(bucket)
|
281
|
+
data << object
|
282
|
+
data << params
|
283
|
+
Utils::join_urn(*data)
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
240
287
|
end
|
241
288
|
end
|
242
289
|
end
|
data/lib/right_aws_api.rb
CHANGED
@@ -40,6 +40,7 @@ require "cloud/aws/iam/manager"
|
|
40
40
|
require "cloud/aws/rds/manager"
|
41
41
|
require "cloud/aws/route53/manager"
|
42
42
|
require "cloud/aws/s3/manager"
|
43
|
+
require "cloud/aws/s3/link/manager"
|
43
44
|
require "cloud/aws/sdb/manager"
|
44
45
|
require "cloud/aws/sns/manager"
|
45
46
|
require "cloud/aws/sqs/manager"
|
data/right_aws_api.gemspec
CHANGED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'right_aws_api'
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
|
5
|
+
describe RightScale::CloudApi::AWS::S3::Link::RequestSigner do
|
6
|
+
|
7
|
+
|
8
|
+
context '#compute_params!' do
|
9
|
+
before :each do
|
10
|
+
@access_key = 'access-key'
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'Expires' do
|
14
|
+
it 'defaults to something in the future' do
|
15
|
+
result = subject.compute_params!({}, @access_key)
|
16
|
+
expect(result['Expires']).to be_an(Integer)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'sets the passed value' do
|
20
|
+
expectation = 123
|
21
|
+
result = subject.compute_params!({ 'Expires' => expectation }, @access_key)
|
22
|
+
expect(result['Expires']).to eq(expectation)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
context 'AWSAccessKeyId' do
|
28
|
+
it 'sets the passed value' do
|
29
|
+
result = subject.compute_params!({}, @access_key)
|
30
|
+
expect(result['AWSAccessKeyId']).to eq(@access_key)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
context '#compute_signature' do
|
38
|
+
before :each do
|
39
|
+
@secret_key = 'secret-key'
|
40
|
+
@verb = :get
|
41
|
+
@bucket = 'foo-bar'
|
42
|
+
@object = 'foo%2Fbar%2F%D0%B1%D0%B0%D0%BD%D0%B0%D0%BD%D0%B0.jpg'
|
43
|
+
@params = { 'Foo' => 1, 'Bar' => 2, 'Expires' => 1000000 }
|
44
|
+
@expectation = "EShMsLs2Bqak5YuIqOTJq15qcJE="
|
45
|
+
@result = subject.compute_signature(@secret_key, @verb, @bucket, @object, @params)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'properly calculates the signature' do
|
49
|
+
expect(@result).to eq(@expectation)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'right_aws_api'
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
|
5
|
+
describe RightScale::CloudApi::AWS::S3::RequestSigner do
|
6
|
+
context '#get_subresources' do
|
7
|
+
before :each do
|
8
|
+
@sub_resources = {
|
9
|
+
'acl' => 0,
|
10
|
+
'policy' => 0,
|
11
|
+
'versions' => 0,
|
12
|
+
'website' => 0,
|
13
|
+
'response-content-type' => 0,
|
14
|
+
'response-content-language' => 0,
|
15
|
+
'response-foo-bar' => 0,
|
16
|
+
}
|
17
|
+
trash = {
|
18
|
+
'foo' => 0,
|
19
|
+
'bar' => 0,
|
20
|
+
}
|
21
|
+
params = @sub_resources.merge(trash)
|
22
|
+
@result = subject.get_subresources(params)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "extracts SUB_RESOURCES and response- params" do
|
26
|
+
expect(@result).to eq(@sub_resources)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
context '#compute_canonicalized_bucket' do
|
32
|
+
context 'DNS bucket' do
|
33
|
+
it 'adds a trailing slash' do
|
34
|
+
expect(subject.compute_canonicalized_bucket('foo-bar')).to eq('foo-bar/')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'non DNS bucket' do
|
39
|
+
it 'does nothing' do
|
40
|
+
expect(subject.compute_canonicalized_bucket('foo_bar')).to eq('foo_bar')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
context '#compute_canonicalized_path' do
|
47
|
+
context 'with no sub-resources' do
|
48
|
+
before :each do
|
49
|
+
bucket = 'foo-bar_bucket'
|
50
|
+
relative_path = 'a/b/c/d.jpg'
|
51
|
+
params = { 'Foo' => 1, 'acl' => '2', 'response-content-type' => 'jpg' }
|
52
|
+
@result = subject.compute_canonicalized_path(bucket, relative_path, params)
|
53
|
+
@expectation = '/foo-bar_bucket/a/b/c/d.jpg?acl=2&response-content-type=jpg'
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'works' do
|
57
|
+
expect(@result).to eq(@expectation)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
context '#compute_bucket_name_and_object_path' do
|
65
|
+
before :each do
|
66
|
+
@original_path = 'my-test-bucket/foo/bar/банана.jpg'
|
67
|
+
@bucket = 'my-test-bucket'
|
68
|
+
@relative_path = 'foo%2Fbar%2F%D0%B1%D0%B0%D0%BD%D0%B0%D0%BD%D0%B0.jpg'
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when this is a first API call attempt' do
|
72
|
+
before :each do
|
73
|
+
@r_bucket, @r_path = subject.compute_bucket_name_and_object_path(nil, @original_path)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "extracts the bucket and escapes the path" do
|
77
|
+
expect(@r_bucket).to eq(@bucket)
|
78
|
+
expect(@r_path).to eq(@relative_path)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
context 'when there is a redirect and the bucket is already extracted' do
|
84
|
+
before :each do
|
85
|
+
@r_bucket, @r_path = subject.compute_bucket_name_and_object_path(@bucket, @relative_path)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "does nothing" do
|
89
|
+
expect(@r_bucket).to eq(@bucket)
|
90
|
+
expect(@r_path).to eq(@relative_path)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
context '#compute_host' do
|
97
|
+
context 'DNS bucket' do
|
98
|
+
before :each do
|
99
|
+
bucket = 'foo-bar-bucket'
|
100
|
+
uri = URI.parse('https://a.b.com')
|
101
|
+
@result = subject.compute_host(bucket, uri)
|
102
|
+
@expectation = 'https://foo-bar-bucket.a.b.com'
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'prepends the host name with the bucket name' do
|
106
|
+
expect(@result.to_s).to eq(@expectation)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
context 'non DNS bucket' do
|
112
|
+
before :each do
|
113
|
+
bucket = 'foo-bar_bucket'
|
114
|
+
uri = URI.parse('https://a.b.com')
|
115
|
+
@result = subject.compute_host(bucket, uri)
|
116
|
+
@expectation = 'https://a.b.com'
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'has no effect on the host name' do
|
120
|
+
expect(@result.to_s).to eq(@expectation)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
context '#compute_body' do
|
127
|
+
it 'has no effect for non Hash body' do
|
128
|
+
expect(subject.compute_body(nil, 'application/json')).to eq(nil)
|
129
|
+
expect(subject.compute_body(1, 'application/json')).to eq(1)
|
130
|
+
expect(subject.compute_body([1], 'application/json')).to eq([1])
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'converts Hashes to the required content type' do
|
134
|
+
expect(subject.compute_body({1 => 2}, 'application/json')).to eq("{\"1\":2}")
|
135
|
+
expect(subject.compute_body({1 => 2}, 'application/xml')).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<1>2</1>")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
context '#compute_headers!' do
|
141
|
+
before :each do
|
142
|
+
@headers = RightScale::CloudApi::HTTPHeaders.new
|
143
|
+
@body = nil
|
144
|
+
@host = 'foo_bar.host.com'
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'content-type' do
|
148
|
+
it 'defaults content-type to application/octet-stream' do
|
149
|
+
result = subject.compute_headers!(@headers, @body, @host)
|
150
|
+
expectation = ['application/octet-stream']
|
151
|
+
expect(result['content-type']).to eq(expectation)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
context 'date' do
|
157
|
+
it 'sets date' do
|
158
|
+
result = subject.compute_headers!(@headers, @body, @host)
|
159
|
+
expect(result['date']).to be_an(Array)
|
160
|
+
expect(result['date'].first).to be_a(String)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
context 'content-md5' do
|
166
|
+
context 'body is blank' do
|
167
|
+
it 'does not set the header' do
|
168
|
+
result = subject.compute_headers!(@headers, @body, @host)
|
169
|
+
expect(result['content-md5']).to eq([])
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
context 'body is not blank' do
|
175
|
+
it 'does not set the header' do
|
176
|
+
result = subject.compute_headers!(@headers, 'woo-hoo', @host)
|
177
|
+
expect(result['content-md5']).to eq(['Ezs4dVuMkr7EgUDB41SEMg=='])
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
context 'host' do
|
184
|
+
it 'sets the host' do
|
185
|
+
result = subject.compute_headers!(@headers, @body, @host)
|
186
|
+
expect(result['host']).to eq([@host])
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
context '#compute_signature' do
|
193
|
+
before :each do
|
194
|
+
@secret_key = 'secret-key'
|
195
|
+
@verb = :get
|
196
|
+
@bucket = 'foo-bar'
|
197
|
+
@object = 'foo%2Fbar%2F%D0%B1%D0%B0%D0%BD%D0%B0%D0%BD%D0%B0.jpg'
|
198
|
+
@params = { 'Foo' => 1, 'Bar' => 2}
|
199
|
+
@headers = RightScale::CloudApi::HTTPHeaders.new(
|
200
|
+
'x-amz-foo-bar' => '1',
|
201
|
+
'x-amx-foo-boo' => '2',
|
202
|
+
'date' => 'Fri, 11 Jul 2014 21:25:46 GMT',
|
203
|
+
'other-header' => 'moo'
|
204
|
+
)
|
205
|
+
@expectation = "Z7hSptZVg7WytxFfM7K73henBpA="
|
206
|
+
@result = subject.compute_signature(@access_key, @secret_key, @verb, @bucket, @object, @params, @headers)
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'properly calculates the signature' do
|
210
|
+
expect(@result).to eq(@expectation)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
context '#compute_path' do
|
216
|
+
before :each do
|
217
|
+
@path = 'foo-bar'
|
218
|
+
@object = 'foo%2Fbar%2F%D0%B1%D0%B0%D0%BD%D0%B0%D0%BD%D0%B0.jpg'
|
219
|
+
@params = { 'Foo' => 1, 'Bar' => 2}
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'works for DNS bucket' do
|
223
|
+
expect(subject.compute_path('foo-bar', @object, @params)).to eq('/foo%2Fbar%2F%D0%B1%D0%B0%D0%BD%D0%B0%D0%BD%D0%B0.jpg?Bar=2&Foo=1')
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'works for non DNS bucket' do
|
227
|
+
expect(subject.compute_path('foo_bar', @object, @params)).to eq('/foo_bar/foo%2Fbar%2F%D0%B1%D0%B0%D0%BD%D0%B0%D0%BD%D0%B0.jpg?Bar=2&Foo=1')
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: right_aws_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- RightScale, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: right_cloud_api_base
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.0.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.0.0
|
41
55
|
description: |+
|
42
56
|
== DESCRIPTION:
|
43
57
|
|
@@ -75,6 +89,9 @@ files:
|
|
75
89
|
- lib/cloud/aws/route53/manager.rb
|
76
90
|
- lib/cloud/aws/route53/routines/request_signer.rb
|
77
91
|
- lib/cloud/aws/route53/wrappers/default.rb
|
92
|
+
- lib/cloud/aws/s3/link/manager.rb
|
93
|
+
- lib/cloud/aws/s3/link/routines/request_signer.rb
|
94
|
+
- lib/cloud/aws/s3/link/wrappers/default.rb
|
78
95
|
- lib/cloud/aws/s3/manager.rb
|
79
96
|
- lib/cloud/aws/s3/parsers/response_error.rb
|
80
97
|
- lib/cloud/aws/s3/routines/request_signer.rb
|
@@ -85,6 +102,8 @@ files:
|
|
85
102
|
- lib/right_aws_api.rb
|
86
103
|
- lib/right_aws_api_version.rb
|
87
104
|
- right_aws_api.gemspec
|
105
|
+
- spec/cloud/aws/s3/link/routines/request_signer_spec.rb
|
106
|
+
- spec/cloud/aws/s3/routines/request_signer_spec.rb
|
88
107
|
- spec/describe_calls.rb
|
89
108
|
homepage:
|
90
109
|
licenses: []
|
@@ -114,5 +133,7 @@ signing_key:
|
|
114
133
|
specification_version: 4
|
115
134
|
summary: The gem provides interface to AWS cloud services.
|
116
135
|
test_files:
|
136
|
+
- spec/cloud/aws/s3/routines/request_signer_spec.rb
|
137
|
+
- spec/cloud/aws/s3/link/routines/request_signer_spec.rb
|
117
138
|
- spec/describe_calls.rb
|
118
139
|
has_rdoc:
|