api-auth 2.2.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -2
  3. data/.rubocop_todo.yml +51 -9
  4. data/.travis.yml +9 -25
  5. data/CHANGELOG.md +36 -0
  6. data/Gemfile +1 -1
  7. data/README.md +91 -50
  8. data/VERSION +1 -1
  9. data/api_auth.gemspec +7 -5
  10. data/gemfiles/http4.gemfile +3 -3
  11. data/gemfiles/rails_52.gemfile +9 -0
  12. data/gemfiles/rails_60.gemfile +9 -0
  13. data/gemfiles/rails_61.gemfile +11 -0
  14. data/lib/api_auth.rb +1 -0
  15. data/lib/api_auth/base.rb +4 -4
  16. data/lib/api_auth/headers.rb +22 -11
  17. data/lib/api_auth/helpers.rb +2 -2
  18. data/lib/api_auth/railtie.rb +13 -5
  19. data/lib/api_auth/request_drivers/action_controller.rb +9 -8
  20. data/lib/api_auth/request_drivers/curb.rb +4 -4
  21. data/lib/api_auth/request_drivers/faraday.rb +13 -12
  22. data/lib/api_auth/request_drivers/grape_request.rb +87 -0
  23. data/lib/api_auth/request_drivers/http.rb +13 -8
  24. data/lib/api_auth/request_drivers/httpi.rb +9 -8
  25. data/lib/api_auth/request_drivers/net_http.rb +9 -8
  26. data/lib/api_auth/request_drivers/rack.rb +9 -8
  27. data/lib/api_auth/request_drivers/rest_client.rb +9 -8
  28. data/spec/api_auth_spec.rb +15 -8
  29. data/spec/headers_spec.rb +51 -25
  30. data/spec/helpers_spec.rb +1 -1
  31. data/spec/railtie_spec.rb +3 -3
  32. data/spec/request_drivers/action_controller_spec.rb +45 -39
  33. data/spec/request_drivers/action_dispatch_spec.rb +51 -45
  34. data/spec/request_drivers/curb_spec.rb +16 -10
  35. data/spec/request_drivers/faraday_spec.rb +49 -43
  36. data/spec/request_drivers/grape_request_spec.rb +280 -0
  37. data/spec/request_drivers/http_spec.rb +29 -23
  38. data/spec/request_drivers/httpi_spec.rb +28 -22
  39. data/spec/request_drivers/net_http_spec.rb +29 -23
  40. data/spec/request_drivers/rack_spec.rb +41 -35
  41. data/spec/request_drivers/rest_client_spec.rb +42 -36
  42. data/spec/spec_helper.rb +2 -1
  43. metadata +51 -26
  44. data/gemfiles/http2.gemfile +0 -7
  45. data/gemfiles/http3.gemfile +0 -7
  46. data/gemfiles/rails_4.gemfile +0 -11
  47. data/gemfiles/rails_41.gemfile +0 -11
  48. data/gemfiles/rails_42.gemfile +0 -11
  49. data/gemfiles/rails_5.gemfile +0 -11
  50. data/gemfiles/rails_51.gemfile +0 -9
  51. data/spec/.rubocop.yml +0 -5
@@ -20,7 +20,7 @@ describe ApiAuth::RequestDrivers::FaradayRequest do
20
20
  let(:request_headers) do
21
21
  {
22
22
  'Authorization' => 'APIAuth 1044:12345',
23
- 'Content-MD5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
23
+ 'X-Authorization-Content-SHA256' => '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=',
24
24
  'content-type' => 'text/plain',
25
25
  'DATE' => timestamp
26
26
  }
@@ -44,8 +44,8 @@ describe ApiAuth::RequestDrivers::FaradayRequest do
44
44
  expect(driven_request.content_type).to eq('text/plain')
45
45
  end
46
46
 
47
- it 'gets the content_md5' do
48
- expect(driven_request.content_md5).to eq('1B2M2Y8AsgTpgAmY7PhCfg==')
47
+ it 'gets the content_hash' do
48
+ expect(driven_request.content_hash).to eq('47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=')
49
49
  end
50
50
 
51
51
  it 'gets the request_uri' do
@@ -60,14 +60,14 @@ describe ApiAuth::RequestDrivers::FaradayRequest do
60
60
  expect(driven_request.authorization_header).to eq('APIAuth 1044:12345')
61
61
  end
62
62
 
63
- describe '#calculated_md5' do
64
- it 'calculates md5 from the body' do
65
- expect(driven_request.calculated_md5).to eq('kZXQvrKoieG+Be1rsZVINw==')
63
+ describe '#calculated_hash' do
64
+ it 'calculates hash from the body' do
65
+ expect(driven_request.calculated_hash).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
66
66
  end
67
67
 
68
68
  it 'treats no body as empty string' do
69
69
  request.body = nil
70
- expect(driven_request.calculated_md5).to eq('1B2M2Y8AsgTpgAmY7PhCfg==')
70
+ expect(driven_request.calculated_hash).to eq('47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=')
71
71
  end
72
72
  end
73
73
 
@@ -115,46 +115,46 @@ describe ApiAuth::RequestDrivers::FaradayRequest do
115
115
  }
116
116
  end
117
117
 
118
- describe '#populate_content_md5' do
118
+ describe '#populate_content_hash' do
119
119
  context 'when getting' do
120
- it "doesn't populate content-md5" do
121
- request.method = :get
122
- driven_request.populate_content_md5
123
- expect(request.headers['Content-MD5']).to be_nil
120
+ it "doesn't populate content hash" do
121
+ request.http_method = :get
122
+ driven_request.populate_content_hash
123
+ expect(request.headers['X-Authorization-Content-SHA256']).to be_nil
124
124
  end
125
125
  end
126
126
 
127
127
  context 'when posting' do
128
- it 'populates content-md5' do
129
- request.method = :post
130
- driven_request.populate_content_md5
131
- expect(request.headers['Content-MD5']).to eq('kZXQvrKoieG+Be1rsZVINw==')
128
+ it 'populates content hash' do
129
+ request.http_method = :post
130
+ driven_request.populate_content_hash
131
+ expect(request.headers['X-Authorization-Content-SHA256']).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
132
132
  end
133
133
 
134
134
  it 'refreshes the cached headers' do
135
- driven_request.populate_content_md5
136
- expect(driven_request.content_md5).to eq('kZXQvrKoieG+Be1rsZVINw==')
135
+ driven_request.populate_content_hash
136
+ expect(driven_request.content_hash).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
137
137
  end
138
138
  end
139
139
 
140
140
  context 'when putting' do
141
- it 'populates content-md5' do
142
- request.method = :put
143
- driven_request.populate_content_md5
144
- expect(request.headers['Content-MD5']).to eq('kZXQvrKoieG+Be1rsZVINw==')
141
+ it 'populates content hash' do
142
+ request.http_method = :put
143
+ driven_request.populate_content_hash
144
+ expect(request.headers['X-Authorization-Content-SHA256']).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
145
145
  end
146
146
 
147
147
  it 'refreshes the cached headers' do
148
- driven_request.populate_content_md5
149
- expect(driven_request.content_md5).to eq('kZXQvrKoieG+Be1rsZVINw==')
148
+ driven_request.populate_content_hash
149
+ expect(driven_request.content_hash).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
150
150
  end
151
151
  end
152
152
 
153
153
  context 'when deleting' do
154
- it "doesn't populate content-md5" do
155
- request.method = :delete
156
- driven_request.populate_content_md5
157
- expect(request.headers['Content-MD5']).to be_nil
154
+ it "doesn't populate content hash" do
155
+ request.http_method = :delete
156
+ driven_request.populate_content_hash
157
+ expect(request.headers['X-Authorization-Content-SHA256']).to be_nil
158
158
  end
159
159
  end
160
160
  end
@@ -183,77 +183,83 @@ describe ApiAuth::RequestDrivers::FaradayRequest do
183
183
  end
184
184
  end
185
185
 
186
- describe 'md5_mismatch?' do
186
+ describe 'content_hash_mismatch?' do
187
187
  context 'when getting' do
188
188
  before do
189
- request.method = :get
189
+ request.http_method = :get
190
190
  end
191
191
 
192
192
  it 'is false' do
193
- expect(driven_request.md5_mismatch?).to be false
193
+ expect(driven_request.content_hash_mismatch?).to be false
194
194
  end
195
195
  end
196
196
 
197
197
  context 'when posting' do
198
198
  before do
199
- request.method = :post
199
+ request.http_method = :post
200
200
  end
201
201
 
202
202
  context 'when calculated matches sent' do
203
203
  before do
204
- request.headers['Content-MD5'] = 'kZXQvrKoieG+Be1rsZVINw=='
204
+ request.headers['X-Authorization-Content-SHA256'] = 'JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='
205
205
  end
206
206
 
207
207
  it 'is false' do
208
- expect(driven_request.md5_mismatch?).to be false
208
+ expect(driven_request.content_hash_mismatch?).to be false
209
209
  end
210
210
  end
211
211
 
212
212
  context "when calculated doesn't match sent" do
213
213
  before do
214
- request.headers['Content-MD5'] = '3'
214
+ request.headers['X-Authorization-Content-SHA256'] = '3'
215
215
  end
216
216
 
217
217
  it 'is true' do
218
- expect(driven_request.md5_mismatch?).to be true
218
+ expect(driven_request.content_hash_mismatch?).to be true
219
219
  end
220
220
  end
221
221
  end
222
222
 
223
223
  context 'when putting' do
224
224
  before do
225
- request.method = :put
225
+ request.http_method = :put
226
226
  end
227
227
 
228
228
  context 'when calculated matches sent' do
229
229
  before do
230
- request.headers['Content-MD5'] = 'kZXQvrKoieG+Be1rsZVINw=='
230
+ request.headers['X-Authorization-Content-SHA256'] = 'JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='
231
231
  end
232
232
 
233
233
  it 'is false' do
234
- expect(driven_request.md5_mismatch?).to be false
234
+ expect(driven_request.content_hash_mismatch?).to be false
235
235
  end
236
236
  end
237
237
 
238
238
  context "when calculated doesn't match sent" do
239
239
  before do
240
- request.headers['Content-MD5'] = '3'
240
+ request.headers['X-Authorization-Content-SHA256'] = '3'
241
241
  end
242
242
 
243
243
  it 'is true' do
244
- expect(driven_request.md5_mismatch?).to be true
244
+ expect(driven_request.content_hash_mismatch?).to be true
245
245
  end
246
246
  end
247
247
  end
248
248
 
249
249
  context 'when deleting' do
250
250
  before do
251
- request.method = :delete
251
+ request.http_method = :delete
252
252
  end
253
253
 
254
254
  it 'is false' do
255
- expect(driven_request.md5_mismatch?).to be false
255
+ expect(driven_request.content_hash_mismatch?).to be false
256
256
  end
257
257
  end
258
258
  end
259
+
260
+ describe 'fetch_headers' do
261
+ it 'returns request headers' do
262
+ expect(driven_request.fetch_headers).to include('CONTENT-TYPE' => 'text/plain')
263
+ end
264
+ end
259
265
  end
@@ -0,0 +1,280 @@
1
+ require 'spec_helper'
2
+
3
+ describe ApiAuth::RequestDrivers::GrapeRequest do
4
+ let(:default_method) { 'PUT' }
5
+ let(:default_params) do
6
+ { 'message' => "hello\nworld" }
7
+ end
8
+ let(:default_options) do
9
+ {
10
+ method: method,
11
+ params: params
12
+ }
13
+ end
14
+ let(:default_env) do
15
+ Rack::MockRequest.env_for('/', options)
16
+ end
17
+ let(:method) { default_method }
18
+ let(:params) { default_params }
19
+ let(:options) { default_options.merge(request_headers) }
20
+ let(:env) { default_env }
21
+
22
+ let(:request) do
23
+ Grape::Request.new(env)
24
+ end
25
+
26
+ let(:timestamp) { Time.now.utc.httpdate }
27
+ let(:request_headers) do
28
+ {
29
+ 'HTTP_X_AUTHORIZATION' => 'APIAuth 1044:12345',
30
+ 'HTTP_X_AUTHORIZATION_CONTENT_SHA256' => 'bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=',
31
+ 'HTTP_X_HMAC_CONTENT_TYPE' => 'text/plain',
32
+ 'HTTP_X_HMAC_DATE' => timestamp
33
+ }
34
+ end
35
+
36
+ subject(:driven_request) { ApiAuth::RequestDrivers::GrapeRequest.new(request) }
37
+
38
+ describe 'getting headers correctly' do
39
+ it 'gets the content_type' do
40
+ expect(driven_request.content_type).to eq('text/plain')
41
+ end
42
+
43
+ it 'gets the content_hash' do
44
+ expect(driven_request.content_hash).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
45
+ end
46
+
47
+ it 'gets the request_uri' do
48
+ expect(driven_request.request_uri).to eq('http://example.org/')
49
+ end
50
+
51
+ it 'gets the timestamp' do
52
+ expect(driven_request.timestamp).to eq(timestamp)
53
+ end
54
+
55
+ it 'gets the authorization_header' do
56
+ expect(driven_request.authorization_header).to eq('APIAuth 1044:12345')
57
+ end
58
+
59
+ describe '#calculated_hash' do
60
+ it 'calculates hash from the body' do
61
+ expect(driven_request.calculated_hash).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
62
+ end
63
+
64
+ context 'no body' do
65
+ let(:params) { {} }
66
+
67
+ it 'treats no body as empty string' do
68
+ expect(driven_request.calculated_hash).to eq('47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=')
69
+ end
70
+ end
71
+ end
72
+
73
+ describe 'http_method' do
74
+ context 'when put request' do
75
+ let(:method) { 'put' }
76
+
77
+ it 'returns upcased put' do
78
+ expect(driven_request.http_method).to eq('PUT')
79
+ end
80
+ end
81
+
82
+ context 'when get request' do
83
+ let(:method) { 'get' }
84
+
85
+ it 'returns upcased get' do
86
+ expect(driven_request.http_method).to eq('GET')
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ describe 'setting headers correctly' do
93
+ let(:request_headers) do
94
+ {
95
+ 'HTTP_X_HMAC_CONTENT_TYPE' => 'text/plain'
96
+ }
97
+ end
98
+
99
+ describe '#populate_content_hash' do
100
+ context 'when getting' do
101
+ let(:method) { 'get' }
102
+
103
+ it "doesn't populate content hash" do
104
+ driven_request.populate_content_hash
105
+ expect(request.headers['X-Authorization-Content-Sha256']).to be_nil
106
+ end
107
+ end
108
+
109
+ context 'when posting' do
110
+ let(:method) { 'post' }
111
+
112
+ it 'populates content bash' do
113
+ driven_request.populate_content_hash
114
+ expect(request.headers['X-Authorization-Content-Sha256']).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
115
+ end
116
+
117
+ it 'refreshes the cached headers' do
118
+ driven_request.populate_content_hash
119
+ expect(driven_request.content_hash).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
120
+ end
121
+ end
122
+
123
+ context 'when putting' do
124
+ let(:method) { 'put' }
125
+
126
+ it 'populates content hash' do
127
+ driven_request.populate_content_hash
128
+ expect(request.headers['X-Authorization-Content-Sha256']).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
129
+ end
130
+
131
+ it 'refreshes the cached headers' do
132
+ driven_request.populate_content_hash
133
+ expect(driven_request.content_hash).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
134
+ end
135
+ end
136
+
137
+ context 'when deleting' do
138
+ let(:method) { 'delete' }
139
+
140
+ it "doesn't populate content hash" do
141
+ driven_request.populate_content_hash
142
+ expect(request.headers['X-Authorization-Content-Sha256']).to be_nil
143
+ end
144
+ end
145
+ end
146
+
147
+ describe '#set_date' do
148
+ before do
149
+ allow(Time).to receive_message_chain(:now, :utc, :httpdate).and_return(timestamp)
150
+ end
151
+
152
+ it 'sets the date header of the request' do
153
+ allow(Time).to receive_message_chain(:now, :utc, :httpdate).and_return(timestamp)
154
+ driven_request.set_date
155
+ expect(request.headers['Date']).to eq(timestamp)
156
+ end
157
+
158
+ it 'refreshes the cached headers' do
159
+ driven_request.set_date
160
+ expect(driven_request.timestamp).to eq(timestamp)
161
+ end
162
+ end
163
+
164
+ describe '#set_auth_header' do
165
+ it 'sets the auth header' do
166
+ driven_request.set_auth_header('APIAuth 1044:54321')
167
+ expect(request.headers['Authorization']).to eq('APIAuth 1044:54321')
168
+ end
169
+ end
170
+ end
171
+
172
+ describe 'content_hash_mismatch?' do
173
+ context 'when getting' do
174
+ let(:method) { 'get' }
175
+
176
+ it 'is false' do
177
+ expect(driven_request.content_hash_mismatch?).to be false
178
+ end
179
+ end
180
+
181
+ context 'when posting' do
182
+ let(:method) { 'post' }
183
+
184
+ context 'when calculated matches sent' do
185
+ it 'is false' do
186
+ expect(driven_request.content_hash_mismatch?).to be false
187
+ end
188
+ end
189
+
190
+ context "when calculated doesn't match sent" do
191
+ let(:params) { { 'message' => 'hello only' } }
192
+
193
+ it 'is true' do
194
+ expect(driven_request.content_hash_mismatch?).to be true
195
+ end
196
+ end
197
+ end
198
+
199
+ context 'when putting' do
200
+ let(:method) { 'put' }
201
+
202
+ context 'when calculated matches sent' do
203
+ it 'is false' do
204
+ puts driven_request.calculated_hash
205
+ expect(driven_request.content_hash_mismatch?).to be false
206
+ end
207
+ end
208
+
209
+ context "when calculated doesn't match sent" do
210
+ let(:params) { { 'message' => 'hello only' } }
211
+ it 'is true' do
212
+ expect(driven_request.content_hash_mismatch?).to be true
213
+ end
214
+ end
215
+ end
216
+
217
+ context 'when deleting' do
218
+ let(:method) { 'delete' }
219
+
220
+ it 'is false' do
221
+ expect(driven_request.content_hash_mismatch?).to be false
222
+ end
223
+ end
224
+ end
225
+
226
+ describe 'authentics?' do
227
+ let(:request_headers) { {} }
228
+ let(:signed_request) do
229
+ ApiAuth.sign!(request, '1044', '123')
230
+ end
231
+
232
+ context 'when getting' do
233
+ let(:method) { 'get' }
234
+
235
+ it 'validates that the signature in the request header matches the way we sign it' do
236
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
237
+ end
238
+ end
239
+
240
+ context 'when posting' do
241
+ let(:method) { 'post' }
242
+
243
+ it 'validates that the signature in the request header matches the way we sign it' do
244
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
245
+ end
246
+ end
247
+
248
+ context 'when putting' do
249
+ let(:method) { 'put' }
250
+
251
+ let(:signed_request) do
252
+ ApiAuth.sign!(request, '1044', '123')
253
+ end
254
+
255
+ it 'validates that the signature in the request header matches the way we sign it' do
256
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
257
+ end
258
+ end
259
+
260
+ context 'when deleting' do
261
+ let(:method) { 'delete' }
262
+
263
+ let(:signed_request) do
264
+ ApiAuth.sign!(request, '1044', '123')
265
+ end
266
+
267
+ it 'validates that the signature in the request header matches the way we sign it' do
268
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
269
+ end
270
+ end
271
+ end
272
+
273
+ describe 'fetch_headers' do
274
+ it 'returns request headers' do
275
+ expect(driven_request.fetch_headers).to include(
276
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded'
277
+ )
278
+ end
279
+ end
280
+ end