api-auth 2.5.1 → 2.6.0

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.
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'faraday/api_auth'
3
+
4
+ describe Faraday::ApiAuth::Middleware do
5
+ it 'adds the Authorization headers' do
6
+ conn = Faraday.new('http://localhost/') do |f|
7
+ f.request :api_auth, 'foo', 'secret', digest: 'sha256'
8
+ f.adapter :test do |stub|
9
+ stub.get('http://localhost/test') do |env|
10
+ [200, {}, env.request_headers['Authorization']]
11
+ end
12
+ end
13
+ end
14
+ response = conn.get('test', nil, { 'Date' => 'Tue, 02 Aug 2022 09:29:24 GMT' })
15
+ expect(response.body).to eq 'APIAuth-HMAC-SHA256 foo:Tn/lIZ9kphcO32DwG4wFHenqBt37miDEIkA5ykLgGiQ='
16
+ end
17
+ end
data/spec/headers_spec.rb CHANGED
@@ -8,7 +8,7 @@ describe ApiAuth::Headers do
8
8
  let(:uri) { '' }
9
9
 
10
10
  context 'uri with just host without /' do
11
- let(:uri) { 'http://google.com'.freeze }
11
+ let(:uri) { 'https://google.com'.freeze }
12
12
 
13
13
  it 'return / as canonical string path' do
14
14
  expect(subject.canonical_string).to eq('GET,,,/,')
@@ -20,7 +20,7 @@ describe ApiAuth::Headers do
20
20
  end
21
21
 
22
22
  context 'uri with host and /' do
23
- let(:uri) { 'http://google.com/'.freeze }
23
+ let(:uri) { 'https://google.com/'.freeze }
24
24
 
25
25
  it 'return / as canonical string path' do
26
26
  expect(subject.canonical_string).to eq('GET,,,/,')
@@ -31,8 +31,8 @@ describe ApiAuth::Headers do
31
31
  end
32
32
  end
33
33
 
34
- context 'uri has a string matching http:// in it' do
35
- let(:uri) { 'http://google.com/?redirect_to=https://www.example.com'.freeze }
34
+ context 'uri has a string matching https:// in it' do
35
+ let(:uri) { 'https://google.com/?redirect_to=https://www.example.com'.freeze }
36
36
 
37
37
  it 'return /?redirect_to=https://www.example.com as canonical string path' do
38
38
  expect(subject.canonical_string).to eq('GET,,,/?redirect_to=https://www.example.com,')
@@ -46,7 +46,7 @@ describe ApiAuth::Headers do
46
46
 
47
47
  context 'string construction' do
48
48
  context 'with a driver that supplies http_method' do
49
- let(:request) { RestClient::Request.new(url: 'http://google.com', method: :get) }
49
+ let(:request) { RestClient::Request.new(url: 'https://google.com', method: :get) }
50
50
  subject(:headers) { described_class.new(request) }
51
51
  let(:driver) { headers.instance_variable_get('@request') }
52
52
 
@@ -161,7 +161,7 @@ describe ApiAuth::Headers do
161
161
  context 'no content hash already calculated' do
162
162
  let(:request) do
163
163
  RestClient::Request.new(
164
- url: 'http://google.com',
164
+ url: 'https://google.com',
165
165
  method: :post,
166
166
  payload: "hello\nworld"
167
167
  )
@@ -176,7 +176,7 @@ describe ApiAuth::Headers do
176
176
  context 'hash already calculated' do
177
177
  let(:request) do
178
178
  RestClient::Request.new(
179
- url: 'http://google.com',
179
+ url: 'https://google.com',
180
180
  method: :post,
181
181
  payload: "hello\nworld",
182
182
  headers: { 'X-Authorization-Content-SHA256' => 'abcd' }
@@ -191,7 +191,7 @@ describe ApiAuth::Headers do
191
191
  end
192
192
 
193
193
  describe '#content_hash_mismatch?' do
194
- let(:request) { RestClient::Request.new(url: 'http://google.com', method: :get) }
194
+ let(:request) { RestClient::Request.new(url: 'https://google.com', method: :get) }
195
195
  subject(:headers) { described_class.new(request) }
196
196
  let(:driver) { headers.instance_variable_get('@request') }
197
197
 
data/spec/railtie_spec.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'action_controller/test_case'
2
3
 
3
4
  describe 'Rails integration' do
4
5
  API_KEY_STORE = { '1044' => 'l16imAXie1sRMcJODpOG7UwC1VyoqvO13jejkfpKWX4Z09W8DC9IrU23DvCwMry7pgSFW6c5S1GIfV0OY6F/vUA==' }.freeze
@@ -116,7 +117,7 @@ describe 'Rails integration' do
116
117
  describe 'Rails ActiveResource integration' do
117
118
  class TestResource < ActiveResource::Base
118
119
  with_api_auth '1044', API_KEY_STORE['1044']
119
- self.site = 'http://localhost/'
120
+ self.site = 'https://localhost/'
120
121
  self.format = :xml
121
122
  end
122
123
 
@@ -5,6 +5,7 @@ if defined?(ActionDispatch::Request)
5
5
  describe ApiAuth::RequestDrivers::ActionDispatchRequest do
6
6
  let(:timestamp) { Time.now.utc.httpdate }
7
7
  let(:content_sha256) { '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' }
8
+ let(:content_md5) { '+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' }
8
9
 
9
10
  let(:request) do
10
11
  ActionDispatch::Request.new(
@@ -48,7 +49,26 @@ if defined?(ActionDispatch::Request)
48
49
  )
49
50
  end
50
51
 
52
+ let(:request_md5) do
53
+ ActionDispatch::Request.new(
54
+ 'AUTHORIZATION' => 'APIAuth 1044:12345',
55
+ 'PATH_INFO' => '/resource.xml',
56
+ 'QUERY_STRING' => 'foo=bar&bar=foo',
57
+ 'REQUEST_METHOD' => 'PUT',
58
+ 'CONTENT_MD5' => content_md5,
59
+ 'CONTENT_TYPE' => 'text/plain',
60
+ 'CONTENT_LENGTH' => '11',
61
+ 'HTTP_DATE' => timestamp,
62
+ 'rack.input' => StringIO.new("hello\nworld")
63
+ )
64
+ end
65
+
51
66
  subject(:driven_request) { ApiAuth::RequestDrivers::ActionDispatchRequest.new(request) }
67
+ subject(:driven_request_md5) do
68
+ ApiAuth::RequestDrivers::ActionDispatchRequest.new(request_md5,
69
+ authorize_md5: true)
70
+ end
71
+ subject(:driven_request_sha2_with_md5) { ApiAuth::RequestDrivers::ActionDispatchRequest.new(request, authorize_md5: true) }
52
72
 
53
73
  describe 'getting headers correctly' do
54
74
  it 'gets the content_type' do
@@ -69,6 +89,11 @@ if defined?(ActionDispatch::Request)
69
89
  expect(example_request.content_hash).to eq(content_sha256)
70
90
  end
71
91
 
92
+ it 'gets the content_hash for request_md5' do
93
+ example_request = ApiAuth::RequestDrivers::ActionDispatchRequest.new(request_md5, authorize_md5: true)
94
+ expect(example_request.content_hash).to eq(content_md5)
95
+ end
96
+
72
97
  it 'gets the request_uri' do
73
98
  expect(driven_request.request_uri).to eq('/resource.xml?foo=bar&bar=foo')
74
99
  end
@@ -83,13 +108,17 @@ if defined?(ActionDispatch::Request)
83
108
 
84
109
  describe '#calculated_hash' do
85
110
  it 'calculates hash from the body' do
86
- expect(driven_request.calculated_hash).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
111
+ expect(driven_request.calculated_hash).to eq(['JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='])
112
+ end
113
+
114
+ it 'calculates hashes from the body with md5 compatibility option' do
115
+ expect(driven_request_md5.calculated_hash).to eq(%w[JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g= kZXQvrKoieG+Be1rsZVINw==])
87
116
  end
88
117
 
89
118
  it 'treats no body as empty string' do
90
119
  request.env['rack.input'] = StringIO.new
91
120
  request.env['CONTENT_LENGTH'] = 0
92
- expect(driven_request.calculated_hash).to eq('47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=')
121
+ expect(driven_request.calculated_hash).to eq(['47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='])
93
122
  end
94
123
  end
95
124
 
@@ -141,12 +170,12 @@ if defined?(ActionDispatch::Request)
141
170
  it 'populates content hash' do
142
171
  request.env['REQUEST_METHOD'] = 'POST'
143
172
  driven_request.populate_content_hash
144
- expect(request.env['X-AUTHORIZATION-CONTENT-SHA256']).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
173
+ expect(request.env['X-AUTHORIZATION-CONTENT-SHA256']).to eq(['JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='])
145
174
  end
146
175
 
147
176
  it 'refreshes the cached headers' do
148
177
  driven_request.populate_content_hash
149
- expect(driven_request.content_hash).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
178
+ expect(driven_request.content_hash).to eq(['JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='])
150
179
  end
151
180
  end
152
181
 
@@ -154,12 +183,12 @@ if defined?(ActionDispatch::Request)
154
183
  it 'populates content hash' do
155
184
  request.env['REQUEST_METHOD'] = 'PUT'
156
185
  driven_request.populate_content_hash
157
- expect(request.env['X-AUTHORIZATION-CONTENT-SHA256']).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
186
+ expect(request.env['X-AUTHORIZATION-CONTENT-SHA256']).to eq(['JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='])
158
187
  end
159
188
 
160
189
  it 'refreshes the cached headers' do
161
190
  driven_request.populate_content_hash
162
- expect(driven_request.content_hash).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
191
+ expect(driven_request.content_hash).to eq(['JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='])
163
192
  end
164
193
  end
165
194
 
@@ -200,73 +229,129 @@ if defined?(ActionDispatch::Request)
200
229
  context 'when getting' do
201
230
  before do
202
231
  request.env['REQUEST_METHOD'] = 'GET'
232
+ request_md5.env['REQUEST_METHOD'] = 'GET'
203
233
  end
204
234
 
205
235
  it 'is false' do
206
236
  expect(driven_request.content_hash_mismatch?).to be false
207
237
  end
238
+
239
+ it 'is false with md5' do
240
+ expect(driven_request_md5.content_hash_mismatch?).to be false
241
+ end
242
+
243
+ it 'is false with sha2 and md5 compatibility on' do
244
+ expect(driven_request_sha2_with_md5.content_hash_mismatch?).to be false
245
+ end
208
246
  end
209
247
 
210
248
  context 'when posting' do
211
249
  before do
212
250
  request.env['REQUEST_METHOD'] = 'POST'
251
+ request_md5.env['REQUEST_METHOD'] = 'POST'
213
252
  end
214
253
 
215
254
  context 'when calculated matches sent' do
216
255
  before do
217
256
  request.env['X-AUTHORIZATION-CONTENT-SHA256'] = 'JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='
257
+ request_md5.env['CONTENT_MD5'] = 'kZXQvrKoieG+Be1rsZVINw=='
218
258
  end
219
259
 
220
260
  it 'is false' do
221
261
  expect(driven_request.content_hash_mismatch?).to be false
222
262
  end
263
+
264
+ it 'is false with md5' do
265
+ expect(driven_request_md5.content_hash_mismatch?).to be false
266
+ end
267
+
268
+ it 'is false with sha2 and md5 compatibility on' do
269
+ expect(driven_request_sha2_with_md5.content_hash_mismatch?).to be false
270
+ end
223
271
  end
224
272
 
225
273
  context "when calculated doesn't match sent" do
226
274
  before do
227
275
  request.env['X-AUTHORIZATION-CONTENT-SHA256'] = '3'
276
+ request_md5.env['CONTENT_MD5'] = '3'
228
277
  end
229
278
 
230
279
  it 'is true' do
231
280
  expect(driven_request.content_hash_mismatch?).to be true
232
281
  end
282
+
283
+ it 'is true with md5' do
284
+ expect(driven_request.content_hash_mismatch?).to be true
285
+ end
286
+
287
+ it 'is true with sha2 and md5 compatibility on' do
288
+ expect(driven_request_sha2_with_md5.content_hash_mismatch?).to be true
289
+ end
233
290
  end
234
291
  end
235
292
 
236
293
  context 'when putting' do
237
294
  before do
238
295
  request.env['REQUEST_METHOD'] = 'PUT'
296
+ request_md5.env['REQUEST_METHOD'] = 'PUT'
239
297
  end
240
298
 
241
299
  context 'when calculated matches sent' do
242
300
  before do
243
301
  request.env['X-AUTHORIZATION-CONTENT-SHA256'] = 'JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='
302
+ request_md5.env['CONTENT_MD5'] = 'kZXQvrKoieG+Be1rsZVINw=='
244
303
  end
245
304
 
246
305
  it 'is false' do
247
306
  expect(driven_request.content_hash_mismatch?).to be false
248
307
  end
308
+
309
+ it 'is false with md5' do
310
+ expect(driven_request_md5.content_hash_mismatch?).to be false
311
+ end
312
+
313
+ it 'is false with sha2 and md5 compatibility on' do
314
+ expect(driven_request_sha2_with_md5.content_hash_mismatch?).to be false
315
+ end
249
316
  end
250
317
 
251
318
  context "when calculated doesn't match sent" do
252
319
  before do
253
320
  request.env['X-AUTHORIZATION-CONTENT-SHA256'] = '3'
321
+ request_md5.env['CONTENT_MD5'] = '3'
254
322
  end
255
323
 
256
324
  it 'is true' do
257
325
  expect(driven_request.content_hash_mismatch?).to be true
258
326
  end
327
+
328
+ it 'is true with md5' do
329
+ expect(driven_request_md5.content_hash_mismatch?).to be true
330
+ end
331
+
332
+ it 'is true with sha2 and md5 compatibility on' do
333
+ expect(driven_request_sha2_with_md5.content_hash_mismatch?).to be true
334
+ end
259
335
  end
260
336
  end
261
337
 
262
338
  context 'when deleting' do
263
339
  before do
264
340
  request.env['REQUEST_METHOD'] = 'DELETE'
341
+ request_md5.env['REQUEST_METHOD'] = 'DELETE'
265
342
  end
266
343
 
267
344
  it 'is false' do
268
345
  expect(driven_request.content_hash_mismatch?).to be false
269
346
  end
347
+
348
+ it 'is false with md5' do
349
+ expect(driven_request_md5.content_hash_mismatch?).to be false
350
+ end
351
+
352
+ it 'is false with sha2 and md5 compatibility on' do
353
+ expect(driven_request_sha2_with_md5.content_hash_mismatch?).to be false
354
+ end
270
355
  end
271
356
  end
272
357
 
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+
3
+ describe ApiAuth::RequestDrivers::FaradayEnv do
4
+ let(:timestamp) { Time.now.utc.httpdate }
5
+
6
+ let(:request) do
7
+ Faraday::Env.new(verb, body, URI(uri), {}, Faraday::Utils::Headers.new(headers))
8
+ end
9
+
10
+ let(:verb) { :put }
11
+ let(:uri) { 'https://localhost/resource.xml?foo=bar&bar=foo' }
12
+ let(:body) { "hello\nworld" }
13
+
14
+ let(:headers) do
15
+ {
16
+ 'Authorization' => 'APIAuth 1044:12345',
17
+ 'X-Authorization-Content-SHA256' => '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=',
18
+ 'content-type' => 'text/plain',
19
+ 'date' => timestamp
20
+ }
21
+ end
22
+
23
+ subject(:driven_request) { described_class.new(request) }
24
+
25
+ describe 'getting headers correctly' do
26
+ it 'gets the content_type' do
27
+ expect(driven_request.content_type).to eq('text/plain')
28
+ end
29
+
30
+ context 'without Content-Type' do
31
+ let(:headers) { {} }
32
+
33
+ it 'defaults to url-encoded' do
34
+ expect(driven_request.content_type).to eq 'application/x-www-form-urlencoded'
35
+ end
36
+ end
37
+
38
+ it 'gets the content_hash' do
39
+ expect(driven_request.content_hash).to eq('47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=')
40
+ end
41
+
42
+ it 'gets the request_uri' do
43
+ expect(driven_request.request_uri).to eq('/resource.xml?foo=bar&bar=foo')
44
+ end
45
+
46
+ it 'gets the timestamp' do
47
+ expect(driven_request.timestamp).to eq(timestamp)
48
+ end
49
+
50
+ it 'gets the authorization_header' do
51
+ expect(driven_request.authorization_header).to eq('APIAuth 1044:12345')
52
+ end
53
+
54
+ describe '#calculated_hash' do
55
+ it 'calculates hash from the body' do
56
+ expect(driven_request.calculated_hash).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
57
+ expect(driven_request.body.bytesize).to eq(11)
58
+ end
59
+
60
+ context 'no body' do
61
+ let(:body) { nil }
62
+
63
+ it 'treats no body as empty string' do
64
+ expect(driven_request.calculated_hash).to eq('47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=')
65
+ expect(driven_request.body.bytesize).to eq(0)
66
+ end
67
+ end
68
+
69
+ context 'multipart content' do
70
+ let(:body) { File.new('spec/fixtures/upload.png') }
71
+
72
+ it 'calculates correctly for multipart content' do
73
+ expect(driven_request.calculated_hash).to eq('AlKDe7kjMQhuKgKuNG8I7GA93MasHcaVJkJLaUT7+dY=')
74
+ expect(driven_request.body.bytesize).to eq(5112)
75
+ end
76
+ end
77
+ end
78
+
79
+ describe 'http_method' do
80
+ context 'when put request' do
81
+ let(:verb) { :put }
82
+
83
+ it 'returns upcased put' do
84
+ expect(driven_request.http_method).to eq('PUT')
85
+ end
86
+ end
87
+
88
+ context 'when get request' do
89
+ let(:verb) { :get }
90
+
91
+ it 'returns upcased get' do
92
+ expect(driven_request.http_method).to eq('GET')
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ describe 'setting headers correctly' do
99
+ let(:headers) do
100
+ {
101
+ 'content-type' => 'text/plain'
102
+ }
103
+ end
104
+
105
+ describe '#populate_content_hash' do
106
+ context 'when request type has no body' do
107
+ let(:verb) { :get }
108
+
109
+ it "doesn't populate content hash" do
110
+ driven_request.populate_content_hash
111
+ expect(request.request_headers['X-Authorization-Content-SHA256']).to be_nil
112
+ end
113
+ end
114
+
115
+ context 'when request type has a body' do
116
+ let(:verb) { :put }
117
+
118
+ it 'populates content hash' do
119
+ driven_request.populate_content_hash
120
+ expect(request.request_headers['X-Authorization-Content-SHA256']).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
121
+ end
122
+
123
+ it 'refreshes the cached headers' do
124
+ driven_request.populate_content_hash
125
+ expect(driven_request.content_hash).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
126
+ end
127
+ end
128
+ end
129
+
130
+ describe '#set_date' do
131
+ before do
132
+ allow(Time).to receive_message_chain(:now, :utc, :httpdate).and_return(timestamp)
133
+ end
134
+
135
+ it 'sets the date header of the request' do
136
+ driven_request.set_date
137
+ expect(request.request_headers['DATE']).to eq(timestamp)
138
+ end
139
+ end
140
+
141
+ describe '#set_auth_header' do
142
+ it 'sets the auth header' do
143
+ driven_request.set_auth_header('APIAuth 1044:54321')
144
+ expect(request.request_headers['Authorization']).to eq('APIAuth 1044:54321')
145
+ end
146
+ end
147
+ end
148
+
149
+ describe 'content_hash_mismatch?' do
150
+ context 'when request type has no body' do
151
+ let(:verb) { :get }
152
+
153
+ it 'is false' do
154
+ expect(driven_request.content_hash_mismatch?).to be false
155
+ end
156
+ end
157
+
158
+ context 'when request type has a body' do
159
+ let(:verb) { :put }
160
+
161
+ context 'when calculated matches sent' do
162
+ before do
163
+ request.request_headers['X-Authorization-Content-SHA256'] = 'JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='
164
+ end
165
+
166
+ it 'is false' do
167
+ expect(driven_request.content_hash_mismatch?).to be false
168
+ end
169
+ end
170
+
171
+ context "when calculated doesn't match sent" do
172
+ before do
173
+ request['X-Authorization-Content-SHA256'] = '3'
174
+ end
175
+
176
+ it 'is true' do
177
+ expect(driven_request.content_hash_mismatch?).to be true
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ describe 'fetch_headers' do
184
+ it 'returns request headers' do
185
+ expect(driven_request.fetch_headers).to include('CONTENT-TYPE' => 'text/plain')
186
+ end
187
+ end
188
+ end
@@ -13,7 +13,7 @@ describe ApiAuth::RequestDrivers::HttpRequest do
13
13
  end
14
14
 
15
15
  let(:verb) { :put }
16
- let(:uri) { 'http://localhost/resource.xml?foo=bar&bar=foo' }
16
+ let(:uri) { 'https://localhost/resource.xml?foo=bar&bar=foo' }
17
17
  let(:body) { "hello\nworld" }
18
18
 
19
19
  let(:headers) do
@@ -4,7 +4,7 @@ describe ApiAuth::RequestDrivers::HttpiRequest do
4
4
  let(:timestamp) { Time.now.utc.httpdate }
5
5
 
6
6
  let(:request) do
7
- httpi_request = HTTPI::Request.new('http://localhost/resource.xml?foo=bar&bar=foo')
7
+ httpi_request = HTTPI::Request.new('https://localhost/resource.xml?foo=bar&bar=foo')
8
8
  httpi_request.headers.merge!('Authorization' => 'APIAuth 1044:12345',
9
9
  'X-Authorization-Content-SHA256' => '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=',
10
10
  'content-type' => 'text/plain',
@@ -56,7 +56,7 @@ describe ApiAuth::RequestDrivers::HttpiRequest do
56
56
 
57
57
  describe 'setting headers correctly' do
58
58
  let(:request) do
59
- httpi_request = HTTPI::Request.new('http://localhost/resource.xml?foo=bar&bar=foo')
59
+ httpi_request = HTTPI::Request.new('https://localhost/resource.xml?foo=bar&bar=foo')
60
60
  httpi_request.headers['content-type'] = 'text/plain'
61
61
  httpi_request
62
62
  end