api-auth 2.2.0 → 2.4.1

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +11 -52
  3. data/.rubocop_todo.yml +92 -0
  4. data/.travis.yml +15 -14
  5. data/CHANGELOG.md +28 -0
  6. data/Gemfile +1 -1
  7. data/README.md +77 -38
  8. data/VERSION +1 -1
  9. data/api_auth.gemspec +15 -11
  10. data/gemfiles/http2.gemfile +7 -0
  11. data/gemfiles/http3.gemfile +7 -0
  12. data/gemfiles/http4.gemfile +7 -0
  13. data/gemfiles/rails_5.gemfile +5 -7
  14. data/gemfiles/rails_51.gemfile +5 -5
  15. data/gemfiles/rails_52.gemfile +9 -0
  16. data/gemfiles/rails_60.gemfile +11 -0
  17. data/lib/api_auth.rb +3 -0
  18. data/lib/api_auth/base.rb +2 -2
  19. data/lib/api_auth/headers.rb +19 -8
  20. data/lib/api_auth/railtie.rb +9 -5
  21. data/lib/api_auth/request_drivers/action_controller.rb +1 -0
  22. data/lib/api_auth/request_drivers/faraday.rb +2 -1
  23. data/lib/api_auth/request_drivers/grape_request.rb +87 -0
  24. data/lib/api_auth/request_drivers/http.rb +96 -0
  25. data/lib/api_auth/request_drivers/httpi.rb +1 -0
  26. data/lib/api_auth/request_drivers/net_http.rb +1 -1
  27. data/lib/api_auth/request_drivers/rack.rb +1 -0
  28. data/lib/api_auth/request_drivers/rest_client.rb +3 -2
  29. data/spec/api_auth_spec.rb +7 -0
  30. data/spec/headers_spec.rb +26 -8
  31. data/spec/request_drivers/action_controller_spec.rb +10 -4
  32. data/spec/request_drivers/action_dispatch_spec.rb +17 -11
  33. data/spec/request_drivers/curb_spec.rb +9 -3
  34. data/spec/request_drivers/faraday_spec.rb +6 -0
  35. data/spec/request_drivers/grape_request_spec.rb +279 -0
  36. data/spec/request_drivers/http_spec.rb +190 -0
  37. data/spec/request_drivers/httpi_spec.rb +6 -0
  38. data/spec/request_drivers/net_http_spec.rb +6 -0
  39. data/spec/request_drivers/rack_spec.rb +6 -0
  40. data/spec/request_drivers/rest_client_spec.rb +93 -15
  41. data/spec/spec_helper.rb +3 -4
  42. metadata +102 -66
  43. data/gemfiles/rails_4.gemfile +0 -11
  44. data/gemfiles/rails_41.gemfile +0 -11
  45. data/gemfiles/rails_42.gemfile +0 -11
@@ -0,0 +1,190 @@
1
+ require 'spec_helper'
2
+
3
+ describe ApiAuth::RequestDrivers::HttpRequest do
4
+ let(:timestamp) { Time.now.utc.httpdate }
5
+
6
+ let(:request) do
7
+ HTTP::Request.new(
8
+ verb: verb,
9
+ uri: uri,
10
+ headers: headers,
11
+ body: body
12
+ )
13
+ end
14
+
15
+ let(:verb) { :put }
16
+ let(:uri) { 'http://localhost/resource.xml?foo=bar&bar=foo' }
17
+ let(:body) { "hello\nworld" }
18
+
19
+ let(:headers) do
20
+ {
21
+ 'Authorization' => 'APIAuth 1044:12345',
22
+ 'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
23
+ 'content-type' => 'text/plain',
24
+ 'date' => timestamp
25
+ }
26
+ end
27
+
28
+ subject(:driven_request) { described_class.new(request) }
29
+
30
+ describe 'getting headers correctly' do
31
+ it 'gets the content_type' do
32
+ expect(driven_request.content_type).to eq('text/plain')
33
+ end
34
+
35
+ it 'gets the content_md5' do
36
+ expect(driven_request.content_md5).to eq('1B2M2Y8AsgTpgAmY7PhCfg==')
37
+ end
38
+
39
+ it 'gets the request_uri' do
40
+ expect(driven_request.request_uri).to eq('/resource.xml?foo=bar&bar=foo')
41
+ end
42
+
43
+ it 'gets the timestamp' do
44
+ expect(driven_request.timestamp).to eq(timestamp)
45
+ end
46
+
47
+ it 'gets the authorization_header' do
48
+ expect(driven_request.authorization_header).to eq('APIAuth 1044:12345')
49
+ end
50
+
51
+ describe '#calculated_md5' do
52
+ it 'calculates md5 from the body' do
53
+ expect(driven_request.calculated_md5).to eq('kZXQvrKoieG+Be1rsZVINw==')
54
+ expect(driven_request.body.bytesize).to eq(11)
55
+ end
56
+
57
+ context 'no body' do
58
+ let(:body) { nil }
59
+
60
+ it 'treats no body as empty string' do
61
+ expect(driven_request.calculated_md5).to eq('1B2M2Y8AsgTpgAmY7PhCfg==')
62
+ expect(driven_request.body.bytesize).to eq(0)
63
+ end
64
+ end
65
+
66
+ context 'multipart content' do
67
+ let(:body) { File.new('spec/fixtures/upload.png') }
68
+
69
+ it 'calculates correctly for multipart content' do
70
+ expect(driven_request.calculated_md5).to eq('k4U8MTA3RHDcewBzymVNEQ==')
71
+ expect(driven_request.body.bytesize).to eq(5112)
72
+ end
73
+ end
74
+ end
75
+
76
+ describe 'http_method' do
77
+ context 'when put request' do
78
+ let(:verb) { :put }
79
+
80
+ it 'returns upcased put' do
81
+ expect(driven_request.http_method).to eq('PUT')
82
+ end
83
+ end
84
+
85
+ context 'when get request' do
86
+ let(:verb) { :get }
87
+
88
+ it 'returns upcased get' do
89
+ expect(driven_request.http_method).to eq('GET')
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ describe 'setting headers correctly' do
96
+ let(:headers) do
97
+ {
98
+ 'content-type' => 'text/plain'
99
+ }
100
+ end
101
+
102
+ describe '#populate_content_md5' do
103
+ context 'when request type has no body' do
104
+ let(:verb) { :get }
105
+
106
+ it "doesn't populate content-md5" do
107
+ driven_request.populate_content_md5
108
+ expect(request['Content-MD5']).to be_nil
109
+ end
110
+ end
111
+
112
+ context 'when request type has a body' do
113
+ let(:verb) { :put }
114
+
115
+ it 'populates content-md5' do
116
+ driven_request.populate_content_md5
117
+ expect(request['Content-MD5']).to eq('kZXQvrKoieG+Be1rsZVINw==')
118
+ end
119
+
120
+ it 'refreshes the cached headers' do
121
+ driven_request.populate_content_md5
122
+ expect(driven_request.content_md5).to eq('kZXQvrKoieG+Be1rsZVINw==')
123
+ end
124
+ end
125
+ end
126
+
127
+ describe '#set_date' do
128
+ before do
129
+ allow(Time).to receive_message_chain(:now, :utc, :httpdate).and_return(timestamp)
130
+ end
131
+
132
+ it 'sets the date header of the request' do
133
+ driven_request.set_date
134
+ expect(request['DATE']).to eq(timestamp)
135
+ end
136
+
137
+ it 'refreshes the cached headers' do
138
+ driven_request.set_date
139
+ expect(driven_request.timestamp).to eq(timestamp)
140
+ end
141
+ end
142
+
143
+ describe '#set_auth_header' do
144
+ it 'sets the auth header' do
145
+ driven_request.set_auth_header('APIAuth 1044:54321')
146
+ expect(request['Authorization']).to eq('APIAuth 1044:54321')
147
+ end
148
+ end
149
+ end
150
+
151
+ describe 'md5_mismatch?' do
152
+ context 'when request type has no body' do
153
+ let(:verb) { :get }
154
+
155
+ it 'is false' do
156
+ expect(driven_request.md5_mismatch?).to be false
157
+ end
158
+ end
159
+
160
+ context 'when request type has a body' do
161
+ let(:verb) { :put }
162
+
163
+ context 'when calculated matches sent' do
164
+ before do
165
+ request['Content-MD5'] = 'kZXQvrKoieG+Be1rsZVINw=='
166
+ end
167
+
168
+ it 'is false' do
169
+ expect(driven_request.md5_mismatch?).to be false
170
+ end
171
+ end
172
+
173
+ context "when calculated doesn't match sent" do
174
+ before do
175
+ request['Content-MD5'] = '3'
176
+ end
177
+
178
+ it 'is true' do
179
+ expect(driven_request.md5_mismatch?).to be true
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ describe 'fetch_headers' do
186
+ it 'returns request headers' do
187
+ expect(driven_request.fetch_headers).to include('CONTENT-TYPE' => 'text/plain')
188
+ end
189
+ end
190
+ end
@@ -151,4 +151,10 @@ describe ApiAuth::RequestDrivers::HttpiRequest do
151
151
  end
152
152
  end
153
153
  end
154
+
155
+ describe 'fetch_headers' do
156
+ it 'returns request headers' do
157
+ expect(driven_request.fetch_headers).to include('CONTENT-TYPE' => 'text/plain')
158
+ end
159
+ end
154
160
  end
@@ -193,4 +193,10 @@ describe ApiAuth::RequestDrivers::NetHttpRequest do
193
193
  end
194
194
  end
195
195
  end
196
+
197
+ describe 'fetch_headers' do
198
+ it 'returns request headers' do
199
+ expect(driven_request.fetch_headers).to include('content-type' => ['text/plain'])
200
+ end
201
+ end
196
202
  end
@@ -301,4 +301,10 @@ describe ApiAuth::RequestDrivers::RackRequest do
301
301
  end
302
302
  end
303
303
  end
304
+
305
+ describe 'fetch_headers' do
306
+ it 'returns request headers' do
307
+ expect(driven_request.fetch_headers).to include('CONTENT-TYPE' => 'text/plain')
308
+ end
309
+ end
304
310
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe ApiAuth::RequestDrivers::RestClientRequest do
4
4
  let(:timestamp) { Time.now.utc.httpdate }
5
5
 
6
- let(:request_path) { '/resource.xml?foo=bar&bar=foo' }
6
+ let(:request_path) { 'http://localhost/resource.xml?foo=bar&bar=foo' }
7
7
 
8
8
  let(:request_headers) do
9
9
  {
@@ -16,7 +16,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
16
16
 
17
17
  let(:request) do
18
18
  RestClient::Request.new(
19
- url: '/resource.xml?foo=bar&bar=foo',
19
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
20
20
  headers: request_headers,
21
21
  method: :put,
22
22
  payload: "hello\nworld"
@@ -35,7 +35,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
35
35
  end
36
36
 
37
37
  it 'gets the request_uri' do
38
- expect(driven_request.request_uri).to eq('/resource.xml?foo=bar&bar=foo')
38
+ expect(driven_request.request_uri).to eq('http://localhost/resource.xml?foo=bar&bar=foo')
39
39
  end
40
40
 
41
41
  it 'gets the timestamp' do
@@ -53,7 +53,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
53
53
 
54
54
  it 'treats no body as empty string' do
55
55
  request = RestClient::Request.new(
56
- url: '/resource.xml?foo=bar&bar=foo',
56
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
57
57
  headers: request_headers,
58
58
  method: :put
59
59
  )
@@ -66,7 +66,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
66
66
  context 'when put request' do
67
67
  let(:request) do
68
68
  RestClient::Request.new(
69
- url: '/resource.xml?foo=bar&bar=foo',
69
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
70
70
  headers: request_headers,
71
71
  method: :put
72
72
  )
@@ -80,7 +80,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
80
80
  context 'when get request' do
81
81
  let(:request) do
82
82
  RestClient::Request.new(
83
- url: '/resource.xml?foo=bar&bar=foo',
83
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
84
84
  headers: request_headers,
85
85
  method: :get
86
86
  )
@@ -104,7 +104,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
104
104
  context 'when getting' do
105
105
  let(:request) do
106
106
  RestClient::Request.new(
107
- url: '/resource.xml?foo=bar&bar=foo',
107
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
108
108
  headers: request_headers,
109
109
  method: :get
110
110
  )
@@ -119,7 +119,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
119
119
  context 'when posting' do
120
120
  let(:request) do
121
121
  RestClient::Request.new(
122
- url: '/resource.xml?foo=bar&bar=foo',
122
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
123
123
  headers: request_headers,
124
124
  method: :post,
125
125
  payload: "hello\nworld"
@@ -140,7 +140,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
140
140
  context 'when putting' do
141
141
  let(:request) do
142
142
  RestClient::Request.new(
143
- url: '/resource.xml?foo=bar&bar=foo',
143
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
144
144
  headers: request_headers,
145
145
  method: :put,
146
146
  payload: "hello\nworld"
@@ -161,7 +161,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
161
161
  context 'when deleting' do
162
162
  let(:request) do
163
163
  RestClient::Request.new(
164
- url: '/resource.xml?foo=bar&bar=foo',
164
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
165
165
  headers: request_headers,
166
166
  method: :delete
167
167
  )
@@ -203,7 +203,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
203
203
  context 'when getting' do
204
204
  let(:request) do
205
205
  RestClient::Request.new(
206
- url: '/resource.xml?foo=bar&bar=foo',
206
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
207
207
  headers: request_headers,
208
208
  method: :get
209
209
  )
@@ -217,7 +217,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
217
217
  context 'when posting' do
218
218
  let(:request) do
219
219
  RestClient::Request.new(
220
- url: '/resource.xml?foo=bar&bar=foo',
220
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
221
221
  headers: request_headers,
222
222
  method: :post,
223
223
  payload: "hello\nworld"
@@ -258,7 +258,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
258
258
  context 'when putting' do
259
259
  let(:request) do
260
260
  RestClient::Request.new(
261
- url: '/resource.xml?foo=bar&bar=foo',
261
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
262
262
  headers: request_headers,
263
263
  method: :put,
264
264
  payload: "hello\nworld"
@@ -299,7 +299,7 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
299
299
  context 'when deleting' do
300
300
  let(:request) do
301
301
  RestClient::Request.new(
302
- url: '/resource.xml?foo=bar&bar=foo',
302
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
303
303
  headers: request_headers,
304
304
  method: :delete
305
305
  )
@@ -311,16 +311,94 @@ describe ApiAuth::RequestDrivers::RestClientRequest do
311
311
  end
312
312
  end
313
313
 
314
+ describe 'authentics?' do
315
+ context 'when getting' do
316
+ let(:request) do
317
+ RestClient::Request.new(
318
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
319
+ method: :get
320
+ )
321
+ end
322
+
323
+ let(:signed_request) do
324
+ ApiAuth.sign!(request, '1044', '123')
325
+ end
326
+
327
+ it 'validates that the signature in the request header matches the way we sign it' do
328
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
329
+ end
330
+ end
331
+
332
+ context 'when posting' do
333
+ let(:request) do
334
+ RestClient::Request.new(
335
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
336
+ method: :post,
337
+ payload: "hello\nworld"
338
+ )
339
+ end
340
+
341
+ let(:signed_request) do
342
+ ApiAuth.sign!(request, '1044', '123')
343
+ end
344
+
345
+ it 'validates that the signature in the request header matches the way we sign it' do
346
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
347
+ end
348
+ end
349
+
350
+ context 'when putting' do
351
+ let(:request) do
352
+ RestClient::Request.new(
353
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
354
+ method: :put,
355
+ payload: "hello\nworld"
356
+ )
357
+ end
358
+
359
+ let(:signed_request) do
360
+ ApiAuth.sign!(request, '1044', '123')
361
+ end
362
+
363
+ it 'validates that the signature in the request header matches the way we sign it' do
364
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
365
+ end
366
+ end
367
+
368
+ context 'when deleting' do
369
+ let(:request) do
370
+ RestClient::Request.new(
371
+ url: 'http://localhost/resource.xml?foo=bar&bar=foo',
372
+ method: :delete
373
+ )
374
+ end
375
+
376
+ let(:signed_request) do
377
+ ApiAuth.sign!(request, '1044', '123')
378
+ end
379
+
380
+ it 'validates that the signature in the request header matches the way we sign it' do
381
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
382
+ end
383
+ end
384
+ end
385
+
314
386
  describe 'edge cases' do
315
387
  it "doesn't mess up symbol based headers" do
316
388
  headers = { 'Content-MD5' => 'e59ff97941044f85df5297e1c302d260',
317
389
  :content_type => 'text/plain',
318
390
  'Date' => 'Mon, 23 Jan 1984 03:29:56 GMT' }
319
- request = RestClient::Request.new(url: '/resource.xml?foo=bar&bar=foo',
391
+ request = RestClient::Request.new(url: 'http://localhost/resource.xml?foo=bar&bar=foo',
320
392
  headers: headers,
321
393
  method: :put)
322
394
  ApiAuth.sign!(request, 'some access id', 'some secret key')
323
395
  expect(request.processed_headers).to have_key('Content-Type')
324
396
  end
325
397
  end
398
+
399
+ describe 'fetch_headers' do
400
+ it 'returns request headers' do
401
+ expect(driven_request.fetch_headers).to include('CONTENT-TYPE' => 'text/plain')
402
+ end
403
+ end
326
404
  end
@@ -13,13 +13,12 @@ require 'api_auth'
13
13
  require 'amatch'
14
14
  require 'rest_client'
15
15
  require 'curb'
16
+ require 'http'
16
17
  require 'httpi'
17
18
  require 'faraday'
19
+ require 'grape'
18
20
  require 'net/http/post/multipart'
19
21
 
20
22
  # Requires supporting files with custom matchers and macros, etc,
21
23
  # in ./support/ and its subdirectories.
22
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
23
-
24
- RSpec.configure do |config|
25
- end
24
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f }