api-auth 2.2.1 → 2.5.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.
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