right_aws_api 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -61,11 +61,11 @@ module RightScale
61
61
 
62
62
 
63
63
  base.query_api_pattern 'DeleteBucketPolicy', :delete, '{:Bucket}',
64
- :params => { 'policy' => nil }
64
+ :params => { 'policy' => '' }
65
65
 
66
66
 
67
67
  base.query_api_pattern 'DeleteBucketWebsite', :delete, '{:Bucket}',
68
- :params => { 'website' => nil }
68
+ :params => { 'website' => '' }
69
69
 
70
70
 
71
71
  base.query_api_pattern 'ListObjects', :get, '{:Bucket}'
@@ -73,130 +73,131 @@ module RightScale
73
73
 
74
74
 
75
75
  base.query_api_pattern 'GetBucketAcl', :get, '{:Bucket}',
76
- :params => { 'acl'=> nil }
76
+ :params => { 'acl'=> '' }
77
77
 
78
78
 
79
79
  base.query_api_pattern 'GetBucketPolicy', :get, '{:Bucket}',
80
- :params => { 'policy'=> nil }
80
+ :params => { 'policy'=> '' }
81
81
 
82
82
 
83
83
  base.query_api_pattern 'GetBucketLocation', :get, '{:Bucket}',
84
- :params => { 'location'=> nil }
84
+ :params => { 'location'=> '' }
85
85
 
86
86
 
87
87
  base.query_api_pattern 'GetBucketLogging', :get, '{:Bucket}',
88
- :params => { 'logging'=> nil }
88
+ :params => { 'logging'=> '' }
89
89
 
90
90
 
91
91
  base.query_api_pattern 'GetBucketNotification', :get, '{:Bucket}',
92
- :params => { 'notification'=> nil }
92
+ :params => { 'notification'=> '' }
93
93
 
94
94
 
95
95
  base.query_api_pattern 'GetBucketVersions', :get, '{:Bucket}',
96
- :params => { 'versions'=> nil }
96
+ :params => { 'versions'=> '' }
97
97
 
98
98
 
99
99
  base.query_api_pattern 'GetBucketRequestPayment', :get, '{:Bucket}',
100
- :params => { 'requestPayment'=> nil }
100
+ :params => { 'requestPayment'=> '' }
101
101
 
102
102
 
103
103
  base.query_api_pattern 'GetBucketVersioning', :get, '{:Bucket}',
104
- :params => { 'versioning'=> nil }
104
+ :params => { 'versioning'=> '' }
105
105
 
106
106
 
107
107
  base.query_api_pattern 'GetBucketWebsite', :get, '{:Bucket}',
108
- :params => { 'website'=> nil }
108
+ :params => { 'website'=> '' }
109
109
 
110
110
 
111
111
  base.query_api_pattern 'ListMultipartUploads', :get, '{:Bucket}',
112
- :params => { 'uploads'=> nil }
112
+ :params => { 'uploads'=> '' }
113
113
 
114
114
 
115
- base.query_api_pattern 'PutBucket', :put, '{:Bucket}'
115
+ base.query_api_pattern 'PutBucket', :put, '{:Bucket}',
116
+ :headers => { 'content-type' => 'application/xml' }
116
117
 
117
118
 
118
119
  base.query_api_pattern 'PutBucketAcl', :put, '{:Bucket}',
119
- :params => { 'acl' => nil },
120
+ :params => { 'acl' => '' },
120
121
  :body => { 'AccessControlPolicy' => :AccessControlPolicy },
121
122
  :headers => { 'content-type' => 'application/xml'}
122
123
 
123
124
 
124
125
  base.query_api_pattern 'PutBucketPolicy', :put, '{:Bucket}',
125
- :params => { 'policy' => nil },
126
+ :params => { 'policy' => '' },
126
127
  :body => Utils::MUST_BE_SET,
127
128
  :headers => { 'content-type' => 'application/json' }
128
129
 
129
130
 
130
131
  base.query_api_pattern 'PutBucketLogging', :put, '{:Bucket}',
131
- :params => { 'logging' => nil },
132
+ :params => { 'logging' => '' },
132
133
  :body => { 'BucketLoggingStatus' => :BucketLoggingStatus },
133
134
  :headers => { 'content-type' => 'application/xml'}
134
135
 
135
136
 
136
137
  base.query_api_pattern 'PutBucketNotification', :put, '{:Bucket}',
137
- :params => { 'notification' => nil },
138
+ :params => { 'notification' => '' },
138
139
  :body => { 'NotificationConfiguration' => :NotificationConfiguration },
139
140
  :headers => { 'content-type' => 'application/xml'}
140
141
 
141
142
 
142
143
  base.query_api_pattern 'PutBucketRequestPayment', :put, '{:Bucket}',
143
- :params => { 'requestPayment' => nil },
144
+ :params => { 'requestPayment' => '' },
144
145
  :body => { 'RequestPaymentConfiguration' => :RequestPaymentConfiguration },
145
146
  :headers => { 'content-type' => 'application/xml'}
146
147
 
147
148
 
148
149
  base.query_api_pattern 'PutBucketVersioning', :put, '{:Bucket}',
149
- :params => { 'versioning' => nil },
150
+ :params => { 'versioning' => '' },
150
151
  :body => { 'VersioningConfiguration' => :VersioningConfiguration },
151
152
  :headers => { 'content-type' => 'application/xml'}
152
153
 
153
154
 
154
155
  base.query_api_pattern 'PutBucketWebsite', :put, '{:Bucket}',
155
- :params => { 'website' => nil },
156
+ :params => { 'website' => '' },
156
157
  :body => { 'WebsiteConfiguration' => :WebsiteConfiguration },
157
158
  :headers => { 'content-type' => 'application/xml'}
158
159
 
159
160
 
160
161
  base.query_api_pattern 'GetBucketCors', :get, '{:Bucket}',
161
- :params => { 'cors'=> nil }
162
+ :params => { 'cors'=> '' }
162
163
 
163
164
 
164
165
  base.query_api_pattern 'DeleteBucketCors', :delete, '{:Bucket}',
165
- :params => { 'cors'=> nil }
166
+ :params => { 'cors'=> '' }
166
167
 
167
168
 
168
169
  base.query_api_pattern 'PutBucketCors', :put, '{:Bucket}',
169
- :params => { 'cors'=> nil },
170
+ :params => { 'cors'=> '' },
170
171
  :body => { 'CORSConfiguration' => { 'CORSRule' => :CORSRule } },
171
172
  :headers => { 'content-type' => 'application/xml'}
172
173
 
173
174
 
174
175
  base.query_api_pattern 'GetBucketTagging', :get, '{:Bucket}',
175
- :params => { 'tagging' => nil }
176
+ :params => { 'tagging' => '' }
176
177
 
177
178
 
178
179
  base.query_api_pattern 'PutBucketTagging', :put, '{:Bucket}',
179
- :params => { 'tagging' => nil },
180
+ :params => { 'tagging' => '' },
180
181
  :body => { 'Tagging' => { 'TagSet' => { 'Tag' => :TagSet } } },
181
182
  :headers => { 'content-type' => 'application/xml'}
182
183
 
183
184
 
184
185
  base.query_api_pattern 'DeleteBucketTagging', :delete, '{:Bucket}',
185
- :params => { 'tagging' => nil }
186
+ :params => { 'tagging' => '' }
186
187
 
187
188
 
188
189
  base.query_api_pattern 'GetBucketLifecycle', :get, '{:Bucket}',
189
- :params => { 'lifecycle' => nil }
190
+ :params => { 'lifecycle' => '' }
190
191
 
191
192
 
192
193
  base.query_api_pattern 'PutBucketLifecycle', :put, '{:Bucket}',
193
- :params => { 'lifecycle' => nil },
194
+ :params => { 'lifecycle' => '' },
194
195
  :body => { 'LifecycleConfiguration' => { 'Rule' => :Rule } },
195
196
  :headers => { 'content-type' => 'application/xml'}
196
197
 
197
198
 
198
199
  base.query_api_pattern 'DeleteBucketLifecycle', :delete, '{:Bucket}',
199
- :params => { 'lifecycle' => nil }
200
+ :params => { 'lifecycle' => '' }
200
201
 
201
202
 
202
203
  #-----------------
@@ -208,7 +209,8 @@ module RightScale
208
209
 
209
210
 
210
211
  base.query_api_pattern 'DeleteMultipleObjects', :post, '{:Bucket}',
211
- :params => { 'delete' => nil },
212
+ :headers => { 'content-type' => 'application/xml'},
213
+ :params => { 'delete' => '' },
212
214
  :body => { 'Delete' => {
213
215
  'Quiet' => :Quiet,
214
216
  'Object[{:Object}]' => {
@@ -236,11 +238,11 @@ module RightScale
236
238
 
237
239
 
238
240
  base.query_api_pattern 'GetObjectAcl', :get, '{:Bucket}/{:Object}',
239
- :params => { 'acl' => nil }
241
+ :params => { 'acl' => '' }
240
242
 
241
243
 
242
244
  base.query_api_pattern 'GetObjectTorrent', :get, '{:Bucket}/{:Object}',
243
- :params => { 'torrent'=> nil }
245
+ :params => { 'torrent'=> '' }
244
246
 
245
247
 
246
248
  base.query_api_pattern 'HeadObject', :head, '{:Bucket}/{:Object}'
@@ -255,13 +257,13 @@ module RightScale
255
257
 
256
258
 
257
259
  base.query_api_pattern 'PutObjectAcl', :put, '{:Bucket}/{:Object}',
258
- :params => { 'acl'=> nil },
260
+ :params => { 'acl'=> '' },
259
261
  :body => { 'AccessControlPolicy' => :AccessControlPolicy },
260
262
  :headers => { 'content-type' => 'application/xml'}
261
263
 
262
264
 
263
265
  base.query_api_pattern 'PutObjectCannedAcl', :put, '{:Bucket}/{:Object}',
264
- :params => { 'acl' => nil },
266
+ :params => { 'acl' => '' },
265
267
  :headers => { 'x-amz-acl' => :Acl, 'content-type' => 'application/xml'}
266
268
 
267
269
 
@@ -278,7 +280,7 @@ module RightScale
278
280
 
279
281
 
280
282
  base.query_api_pattern 'InitiateMultipartUpload', :post, '{:Bucket}/{:Object}',
281
- :params => { 'uploads' => nil }
283
+ :params => { 'uploads' => '' }
282
284
 
283
285
 
284
286
  base.query_api_pattern 'UploadPart', :post, '{:Bucket}/{:Object}',
@@ -22,7 +22,7 @@
22
22
  #++
23
23
 
24
24
  require "cloud/aws/base/helpers/utils"
25
- require "cloud/aws/base/routines/request_signer"
25
+ require "cloud/aws/sqs/routines/request_signer"
26
26
  require "cloud/aws/base/parsers/response_error"
27
27
 
28
28
  module RightScale
@@ -206,7 +206,7 @@ module RightScale
206
206
 
207
207
  set_routine CloudApi::RetryManager
208
208
  set_routine CloudApi::RequestInitializer
209
- set_routine AWS::RequestSigner
209
+ set_routine AWS::SQS::RequestSigner
210
210
  set_routine CloudApi::RequestGenerator
211
211
  set_routine CloudApi::RequestAnalyzer
212
212
  set_routine CloudApi::ConnectionProxy
@@ -0,0 +1,62 @@
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 SQS
28
+ # SQS Request signer
29
+ class RequestSigner < CloudApi::Routine
30
+
31
+ # RequestSigner error
32
+ class Error < CloudApi::Error
33
+ end
34
+
35
+ # Authenticates an SQS request
36
+ #
37
+ # @return [void]
38
+ #
39
+ # @example
40
+ # no example
41
+ #
42
+ def process
43
+ # Compile a final request path
44
+ @data[:request][:path] = Utils::join_urn(@data[:connection][:uri].path, @data[:request][:relative_path])
45
+
46
+ # Swap query params and body
47
+ @data[:request][:params]['Version'] ||= @data[:options][:api_version]
48
+ #@data[:request][:body] = Utils::params_to_urn(@data[:request][:params]){ |value| Utils::AWS::amz_escape(value) }
49
+ #@data[:request][:params] = {}
50
+
51
+ Utils::AWS::sign_v4_signature(
52
+ @data[:credentials][:aws_access_key_id],
53
+ @data[:credentials][:aws_secret_access_key],
54
+ @data[:connection][:uri].host,
55
+ @data[:request]
56
+ )
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -33,7 +33,7 @@ module RightScale
33
33
  # Gem version namespace
34
34
  module VERSION
35
35
  # Current version
36
- STRING = '0.2.0'
36
+ STRING = '0.2.2'
37
37
  end
38
38
  end
39
39
  end
@@ -35,10 +35,11 @@ Gem::Specification.new do |spec|
35
35
  spec.require_path = 'lib'
36
36
  spec.required_ruby_version = '>= 1.8.7'
37
37
 
38
- spec.add_dependency 'right_cloud_api_base', '>= 0.1.0'
38
+ spec.add_dependency 'right_cloud_api_base', '>= 0.2.1'
39
39
 
40
40
  spec.add_development_dependency 'rake'
41
41
  spec.add_development_dependency 'rspec', '>= 3.0.0'
42
+ spec.add_development_dependency 'timecop'
42
43
 
43
44
  spec.description = <<-EOF
44
45
  == DESCRIPTION:
@@ -0,0 +1,298 @@
1
+ require_relative '../../../../../lib/right_aws_api'
2
+
3
+ require 'rspec'
4
+ require 'timecop'
5
+
6
+ describe RightScale::CloudApi::Utils::AWS do
7
+ subject do
8
+ RightScale::CloudApi::Utils::AWS
9
+ end
10
+
11
+ context "signature v4" do
12
+
13
+ context "sign_v4_get_service_and_region" do
14
+ context "weird unknown host" do
15
+ it "fails" do
16
+ expect{
17
+ subject.sign_v4_get_service_and_region('foo.bar.com')
18
+ }.to raise_error(ArgumentError)
19
+ end
20
+ end
21
+
22
+ context "host with no service" do
23
+ it "fails" do
24
+ expect{
25
+ subject.sign_v4_get_service_and_region('foo.amazonaws.com')
26
+ }.to raise_error(ArgumentError,/service/)
27
+ end
28
+ end
29
+
30
+ context "S3" do
31
+ it "works" do
32
+ expect(
33
+ subject.sign_v4_get_service_and_region('s3.amazonaws.com')
34
+ ).to eq ['s3', 'us-east-1']
35
+
36
+ expect(
37
+ subject.sign_v4_get_service_and_region('xxx.s3.amazonaws.com')
38
+ ).to eq ['s3', 'us-east-1']
39
+
40
+ expect(
41
+ subject.sign_v4_get_service_and_region('s3-external-1.amazonaws.com')
42
+ ).to eq ['s3', 'us-east-1']
43
+
44
+ expect(
45
+ subject.sign_v4_get_service_and_region('s3-region-1.amazonaws.com')
46
+ ).to eq ['s3', 'region-1']
47
+
48
+ expect(
49
+ subject.sign_v4_get_service_and_region('xxx.s3-region-1.amazonaws.com')
50
+ ).to eq ['s3', 'region-1']
51
+
52
+ expect(
53
+ subject.sign_v4_get_service_and_region('s3.region-1.amazonaws.com')
54
+ ).to eq ['s3', 'region-1']
55
+
56
+ expect(
57
+ subject.sign_v4_get_service_and_region('xxx.s3.region-1.amazonaws.com')
58
+ ).to eq ['s3', 'region-1']
59
+ end
60
+ end
61
+
62
+
63
+ context "any other service with standard endpoint" do
64
+ it "works" do
65
+ expect(
66
+ subject.sign_v4_get_service_and_region('ec2.region-1.amazonaws.com')
67
+ ).to eq ['ec2', 'region-1']
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+ context "sign_v4_get_canonical_verb" do
74
+ it "uppercases the given verb" do
75
+ expect( subject.sign_v4_get_canonical_verb(:delete) ).to eq 'DELETE'
76
+ end
77
+ end
78
+
79
+
80
+ context "sign_v4_get_canonical_path" do
81
+ it "does nothing to the given path" do
82
+ expect( subject.sign_v4_get_canonical_path('foo/bar') ).to eq 'foo/bar'
83
+ end
84
+ end
85
+
86
+
87
+ context "sign_v4_get_canonical_headers" do
88
+ it "works" do
89
+ headers = {
90
+ 'foo' => 'x',
91
+ 'bar' => 'y,z'
92
+ }
93
+ expect(
94
+ subject.sign_v4_get_canonical_headers(headers)
95
+ ).to eq "bar:y,z\nfoo:x"
96
+ end
97
+ end
98
+
99
+
100
+ context "sign_v4_get_signed_headers" do
101
+ it "works" do
102
+ headers = {
103
+ 'foo' => 'x',
104
+ 'bar' => 'y,z'
105
+ }
106
+ expect(
107
+ subject.sign_v4_get_signed_headers(headers)
108
+ ).to eq "bar;foo"
109
+ end
110
+ end
111
+
112
+
113
+ context "sign_v4_headers" do
114
+ it "builds credential params" do
115
+ request = {
116
+ :headers => RightScale::CloudApi::HTTPHeaders.new,
117
+ :params => { 'A' => 'B' },
118
+ :body => "banana"
119
+ }
120
+ expect(
121
+ subject.sign_v4_headers(
122
+ request,
123
+ "ec2.region-1.amazonaws.com",
124
+ "20141103T150750Z"
125
+ )
126
+ ).to eq "b493d48364afe44d11c0165cf470a4164d1e2609911ef998be868d46ade3de4e"
127
+
128
+ expect(request).to eq(
129
+ {
130
+ :body => "banana",
131
+ :params => { 'A' => 'B' },
132
+ :headers => {
133
+ "content-length" => [6],
134
+ "content-type" => ["application/x-www-form-urlencoded; charset=utf-8"],
135
+ "content-md5" => ["crMCvyl6Iop1cwEj7+98QQ=="],
136
+ "x-amz-content-sha256" => ["b493d48364afe44d11c0165cf470a4164d1e2609911ef998be868d46ade3de4e"],
137
+ "x-amz-date" => ["20141103T150750Z"],
138
+ "x-amz-expires" => [3600]
139
+ }
140
+ }
141
+ )
142
+ end
143
+ end
144
+
145
+
146
+ context "sign_v4_query_params" do
147
+ it "builds credential params" do
148
+ request = { :params => {} }
149
+ expect(
150
+ subject.sign_v4_query_params(
151
+ request,
152
+ "AWS4-HMAC-SHA256",
153
+ "20141103T150750Z",
154
+ "host;content-type",
155
+ "aws_access_key",
156
+ "20141103/region-1/ec2/aws4_request"
157
+ )
158
+ ).to eq "UNSIGNED-PAYLOAD"
159
+
160
+ expect(request).to eq(
161
+ {
162
+ :params => {
163
+ "X-Amz-Date" => "20141103T150750Z",
164
+ "X-Amz-Expires" => 3600,
165
+ "X-Amz-Algorithm" => "AWS4-HMAC-SHA256",
166
+ "X-Amz-SignedHeaders" => "host;content-type",
167
+ "X-Amz-Credential" => "aws_access_key/20141103/region-1/ec2/aws4_request"
168
+ }
169
+ }
170
+ )
171
+ end
172
+ end
173
+
174
+
175
+ context "sign_v4_get_canonical_string" do
176
+ it "joins things properly" do
177
+ expect(
178
+ subject.sign_v4_get_canonical_string(
179
+ 'verb',
180
+ 'path',
181
+ 'query_string',
182
+ 'headers',
183
+ 'signed_headers',
184
+ 'payload'
185
+ )
186
+ ).to eq "verb\npath\nquery_string\nheaders\n\nsigned_headers\npayload"
187
+ end
188
+ end
189
+
190
+
191
+ context "sign_v4_get_string_to_sign" do
192
+ it "joins things properly" do
193
+ expect(
194
+ subject.sign_v4_get_string_to_sign('algorithm', 'current_time', 'creds_scope', 'canonical_string')
195
+ ).to eq "algorithm\ncurrent_time\ncreds_scope\ne32453154e605024921ee9c5eb4ee7eba458cbd2b387c9ac74e729f9fd7cb81b"
196
+ end
197
+ end
198
+
199
+
200
+ context "sign_v4_get_signature_key" do
201
+ it "works" do
202
+ expect(
203
+ subject.sign_v4_get_signature_key('key', 'string_to_sign', 'date', 'region', 'service')
204
+ ).to eq "0eb6bf1678c804d70bdda801326b3b70080ff19f2cb381e28231b2dcdd445b7c"
205
+ end
206
+ end
207
+
208
+
209
+ context "hex_encode" do
210
+ it "works" do
211
+ expect(
212
+ subject.hex_encode('HelloWorld!')
213
+ ).to eq "48656c6c6f576f726c6421"
214
+ end
215
+ end
216
+
217
+
218
+ context "sign_v4_signature" do
219
+ context "signature in headers" do
220
+ it 'works' do
221
+ request = {
222
+ :verb => :post,
223
+ :path => '/foo/bar',
224
+ :headers => RightScale::CloudApi::HTTPHeaders.new,
225
+ :params => { 'A' => 'B' },
226
+ :body => "banana"
227
+ }
228
+ Timecop.freeze(Time.local(2014)) do
229
+ subject.sign_v4_signature(
230
+ 'aws_access_key',
231
+ 'aws_secret_access_key',
232
+ 'ec2.us-region-1.amazonaws.com',
233
+ request,
234
+ )
235
+ end
236
+ expect(request).to eq(
237
+ {
238
+ :body => "banana",
239
+ :headers => {
240
+ "content-length" => [6],
241
+ "content-type" => ["application/x-www-form-urlencoded; charset=utf-8"],
242
+ "content-md5" => ["crMCvyl6Iop1cwEj7+98QQ=="],
243
+ "x-amz-content-sha256" => ["b493d48364afe44d11c0165cf470a4164d1e2609911ef998be868d46ade3de4e"],
244
+ "x-amz-date" => ["20140101T080000Z"],
245
+ "x-amz-expires" => [3600],
246
+ "host" => ["ec2.us-region-1.amazonaws.com"],
247
+ "authorization" => ["AWS4-HMAC-SHA256 Credential=aws_access_key/20140101/us-region-1/ec2/aws4_request, SignedHeaders=content-length;content-md5;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-expires, Signature=c7814d3865b530322742b73670475ac5adb0c1bd4ab39ecf9b80d672283fc177"]
248
+ },
249
+ :params => {"A" => "B"},
250
+ :path => "/foo/bar?A=B",
251
+ :verb => :post
252
+ }
253
+ )
254
+ end
255
+ end
256
+
257
+
258
+ context "signature in query params" do
259
+ it 'works' do
260
+ request = {
261
+ :verb => :post,
262
+ :path => '/foo/bar',
263
+ :headers => RightScale::CloudApi::HTTPHeaders.new,
264
+ :params => { 'A' => 'B' },
265
+ :body => "banana"
266
+ }
267
+ Timecop.freeze(Time.local(2014)) do
268
+ subject.sign_v4_signature(
269
+ 'aws_access_key',
270
+ 'aws_secret_access_key',
271
+ 'ec2.us-region-1.amazonaws.com',
272
+ request,
273
+ :query_params
274
+ )
275
+ end
276
+ expect(request).to eq(
277
+ {
278
+ :body => "banana",
279
+ :headers => {"host"=>["ec2.us-region-1.amazonaws.com"]},
280
+ :params => {
281
+ "A" => "B",
282
+ "X-Amz-Date" => "20140101T080000Z",
283
+ "X-Amz-Expires" => 3600,
284
+ "X-Amz-Algorithm" => "AWS4-HMAC-SHA256",
285
+ "X-Amz-SignedHeaders" => "host",
286
+ "X-Amz-Credential" => "aws_access_key/20140101/us-region-1/ec2/aws4_request"
287
+ },
288
+ :path => "/foo/bar?A=B&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=aws_access_key%2F20140101%2Fus-region-1%2Fec2%2Faws4_request&X-Amz-Date=20140101T080000Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=0fcac655717af1f947fc0942cc2a2e98fddfe71cf669b0a344235bb83684c9cf",
289
+ :verb => :post
290
+ }
291
+ )
292
+ end
293
+ end
294
+
295
+ end
296
+
297
+ end
298
+ end