Skip to content

Commit a618e15

Browse files
Samir ALI CHERIFfwininger
authored andcommitted
Add a MD5 compatibility option in authentic? method
1 parent 552cab0 commit a618e15

5 files changed

Lines changed: 110 additions & 16 deletions

File tree

lib/api_auth/base.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ def sign!(request, access_id, secret_key, options = {})
3232
def authentic?(request, secret_key, options = {})
3333
return false if secret_key.nil?
3434

35-
options = { override_http_method: nil }.merge(options)
35+
options = { override_http_method: nil, authorize_md5: false }.merge(options)
3636

37-
headers = Headers.new(request)
37+
headers = Headers.new(request, authorize_md5: options[:authorize_md5])
3838

3939
# 900 seconds is 15 minutes
4040
clock_skew = options.fetch(:clock_skew, 900)

lib/api_auth/headers.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ module ApiAuth
33
class Headers
44
include RequestDrivers
55

6-
def initialize(request)
6+
def initialize(request, authorize_md5: false)
77
@original_request = request
8-
@request = initialize_request_driver(request)
8+
@request = initialize_request_driver(request, authorize_md5: authorize_md5)
99
true
1010
end
1111

12-
def initialize_request_driver(request)
12+
def initialize_request_driver(request, authorize_md5: false)
1313
new_request =
1414
case request.class.to_s
1515
when /Net::HTTP/
@@ -29,7 +29,7 @@ def initialize_request_driver(request)
2929
when /Grape::Request/
3030
GrapeRequest.new(request)
3131
when /ActionDispatch::Request/
32-
ActionDispatchRequest.new(request)
32+
ActionDispatchRequest.new(request, authorize_md5: authorize_md5)
3333
when /ActionController::CgiRequest/
3434
ActionControllerRequest.new(request)
3535
when /HTTPI::Request/

lib/api_auth/helpers.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ def sha256_base64digest(string)
88
Digest::SHA256.base64digest(string)
99
end
1010

11+
def md5_base64digest(string)
12+
Digest::MD5.base64digest(string)
13+
end
14+
1115
# Capitalizes the keys of a hash
1216
def capitalize_keys(hsh)
1317
capitalized_hash = {}

lib/api_auth/request_drivers/action_controller.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ module RequestDrivers # :nodoc:
33
class ActionControllerRequest # :nodoc:
44
include ApiAuth::Helpers
55

6-
def initialize(request)
6+
def initialize(request, authorize_md5: false)
77
@request = request
8+
@authorize_md5 = authorize_md5
89
fetch_headers
910
true
1011
end
@@ -17,7 +18,9 @@ def set_auth_header(header)
1718

1819
def calculated_hash
1920
body = @request.raw_post
20-
sha256_base64digest(body)
21+
hashes = [sha256_base64digest(body)]
22+
hashes << md5_base64digest(body) if @authorize_md5
23+
hashes
2124
end
2225

2326
def populate_content_hash
@@ -29,7 +32,7 @@ def populate_content_hash
2932

3033
def content_hash_mismatch?
3134
if @request.put? || @request.post?
32-
calculated_hash != content_hash
35+
!calculated_hash.include?(content_hash)
3336
else
3437
false
3538
end
@@ -48,7 +51,9 @@ def content_type
4851
end
4952

5053
def content_hash
51-
find_header(%w[X-AUTHORIZATION-CONTENT-SHA256 X_AUTHORIZATION_CONTENT_SHA256 HTTP_X_AUTHORIZATION_CONTENT_SHA256])
54+
headers = %w[X-AUTHORIZATION-CONTENT-SHA256 X_AUTHORIZATION_CONTENT_SHA256 HTTP_X_AUTHORIZATION_CONTENT_SHA256]
55+
headers += %w[CONTENT-MD5 CONTENT_MD5 HTTP_CONTENT_MD5] if @authorize_md5
56+
find_header(headers)
5257
end
5358

5459
def original_uri

spec/request_drivers/action_dispatch_spec.rb

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
describe ApiAuth::RequestDrivers::ActionDispatchRequest do
66
let(:timestamp) { Time.now.utc.httpdate }
77
let(:content_sha256) { '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' }
8+
let(:content_md5) { '+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' }
89

910
let(:request) do
1011
ActionDispatch::Request.new(
@@ -48,7 +49,26 @@
4849
)
4950
end
5051

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+
5166
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) }
5272

5373
describe 'getting headers correctly' do
5474
it 'gets the content_type' do
@@ -69,6 +89,11 @@
6989
expect(example_request.content_hash).to eq(content_sha256)
7090
end
7191

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+
7297
it 'gets the request_uri' do
7398
expect(driven_request.request_uri).to eq('/resource.xml?foo=bar&bar=foo')
7499
end
@@ -83,13 +108,17 @@
83108

84109
describe '#calculated_hash' do
85110
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==])
87116
end
88117

89118
it 'treats no body as empty string' do
90119
request.env['rack.input'] = StringIO.new
91120
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='])
93122
end
94123
end
95124

@@ -141,25 +170,25 @@
141170
it 'populates content hash' do
142171
request.env['REQUEST_METHOD'] = 'POST'
143172
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='])
145174
end
146175

147176
it 'refreshes the cached headers' do
148177
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='])
150179
end
151180
end
152181

153182
context 'when putting' do
154183
it 'populates content hash' do
155184
request.env['REQUEST_METHOD'] = 'PUT'
156185
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='])
158187
end
159188

160189
it 'refreshes the cached headers' do
161190
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='])
163192
end
164193
end
165194

@@ -200,73 +229,129 @@
200229
context 'when getting' do
201230
before do
202231
request.env['REQUEST_METHOD'] = 'GET'
232+
request_md5.env['REQUEST_METHOD'] = 'GET'
203233
end
204234

205235
it 'is false' do
206236
expect(driven_request.content_hash_mismatch?).to be false
207237
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
208246
end
209247

210248
context 'when posting' do
211249
before do
212250
request.env['REQUEST_METHOD'] = 'POST'
251+
request_md5.env['REQUEST_METHOD'] = 'POST'
213252
end
214253

215254
context 'when calculated matches sent' do
216255
before do
217256
request.env['X-AUTHORIZATION-CONTENT-SHA256'] = 'JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='
257+
request_md5.env['CONTENT_MD5'] = 'kZXQvrKoieG+Be1rsZVINw=='
218258
end
219259

220260
it 'is false' do
221261
expect(driven_request.content_hash_mismatch?).to be false
222262
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
223271
end
224272

225273
context "when calculated doesn't match sent" do
226274
before do
227275
request.env['X-AUTHORIZATION-CONTENT-SHA256'] = '3'
276+
request_md5.env['CONTENT_MD5'] = '3'
228277
end
229278

230279
it 'is true' do
231280
expect(driven_request.content_hash_mismatch?).to be true
232281
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
233290
end
234291
end
235292

236293
context 'when putting' do
237294
before do
238295
request.env['REQUEST_METHOD'] = 'PUT'
296+
request_md5.env['REQUEST_METHOD'] = 'PUT'
239297
end
240298

241299
context 'when calculated matches sent' do
242300
before do
243301
request.env['X-AUTHORIZATION-CONTENT-SHA256'] = 'JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='
302+
request_md5.env['CONTENT_MD5'] = 'kZXQvrKoieG+Be1rsZVINw=='
244303
end
245304

246305
it 'is false' do
247306
expect(driven_request.content_hash_mismatch?).to be false
248307
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
249316
end
250317

251318
context "when calculated doesn't match sent" do
252319
before do
253320
request.env['X-AUTHORIZATION-CONTENT-SHA256'] = '3'
321+
request_md5.env['CONTENT_MD5'] = '3'
254322
end
255323

256324
it 'is true' do
257325
expect(driven_request.content_hash_mismatch?).to be true
258326
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
259335
end
260336
end
261337

262338
context 'when deleting' do
263339
before do
264340
request.env['REQUEST_METHOD'] = 'DELETE'
341+
request_md5.env['REQUEST_METHOD'] = 'DELETE'
265342
end
266343

267344
it 'is false' do
268345
expect(driven_request.content_hash_mismatch?).to be false
269346
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
270355
end
271356
end
272357

0 commit comments

Comments
 (0)