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
@@ -12,18 +12,19 @@ module ApiAuth
12
12
  @request
13
13
  end
14
14
 
15
- def calculated_md5
16
- md5_base64digest(body)
15
+ def calculated_hash
16
+ sha256_base64digest(body)
17
17
  end
18
18
 
19
- def populate_content_md5
19
+ def populate_content_hash
20
20
  return unless %w[POST PUT].include?(http_method)
21
- @request['Content-MD5'] = calculated_md5
21
+
22
+ @request['X-Authorization-Content-SHA256'] = calculated_hash
22
23
  end
23
24
 
24
- def md5_mismatch?
25
+ def content_hash_mismatch?
25
26
  if %w[POST PUT].include?(http_method)
26
- calculated_md5 != content_md5
27
+ calculated_hash != content_hash
27
28
  else
28
29
  false
29
30
  end
@@ -37,8 +38,8 @@ module ApiAuth
37
38
  find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
38
39
  end
39
40
 
40
- def content_md5
41
- find_header(%w[CONTENT-MD5 CONTENT_MD5])
41
+ def content_hash
42
+ find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
42
43
  end
43
44
 
44
45
  def original_uri
@@ -71,6 +72,10 @@ module ApiAuth
71
72
  end
72
73
  end
73
74
 
75
+ def fetch_headers
76
+ capitalize_keys @request.headers.to_h
77
+ end
78
+
74
79
  private
75
80
 
76
81
  def find_header(keys)
@@ -15,19 +15,20 @@ module ApiAuth
15
15
  @request
16
16
  end
17
17
 
18
- def calculated_md5
19
- md5_base64digest(@request.body || '')
18
+ def calculated_hash
19
+ sha256_base64digest(@request.body || '')
20
20
  end
21
21
 
22
- def populate_content_md5
22
+ def populate_content_hash
23
23
  return unless @request.body
24
- @request.headers['Content-MD5'] = calculated_md5
24
+
25
+ @request.headers['X-Authorization-Content-SHA256'] = calculated_hash
25
26
  fetch_headers
26
27
  end
27
28
 
28
- def md5_mismatch?
29
+ def content_hash_mismatch?
29
30
  if @request.body
30
- calculated_md5 != content_md5
31
+ calculated_hash != content_hash
31
32
  else
32
33
  false
33
34
  end
@@ -45,8 +46,8 @@ module ApiAuth
45
46
  find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
46
47
  end
47
48
 
48
- def content_md5
49
- find_header(%w[CONTENT-MD5 CONTENT_MD5])
49
+ def content_hash
50
+ find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
50
51
  end
51
52
 
52
53
  def original_uri
@@ -15,7 +15,7 @@ module ApiAuth
15
15
  @request
16
16
  end
17
17
 
18
- def calculated_md5
18
+ def calculated_hash
19
19
  if @request.respond_to?(:body_stream) && @request.body_stream
20
20
  body = @request.body_stream.read
21
21
  @request.body_stream.rewind
@@ -23,17 +23,18 @@ module ApiAuth
23
23
  body = @request.body
24
24
  end
25
25
 
26
- md5_base64digest(body || '')
26
+ sha256_base64digest(body || '')
27
27
  end
28
28
 
29
- def populate_content_md5
29
+ def populate_content_hash
30
30
  return unless @request.class::REQUEST_HAS_BODY
31
- @request['Content-MD5'] = calculated_md5
31
+
32
+ @request['X-Authorization-Content-SHA256'] = calculated_hash
32
33
  end
33
34
 
34
- def md5_mismatch?
35
+ def content_hash_mismatch?
35
36
  if @request.class::REQUEST_HAS_BODY
36
- calculated_md5 != content_md5
37
+ calculated_hash != content_hash
37
38
  else
38
39
  false
39
40
  end
@@ -51,8 +52,8 @@ module ApiAuth
51
52
  find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
52
53
  end
53
54
 
54
- def content_md5
55
- find_header(%w[CONTENT-MD5 CONTENT_MD5])
55
+ def content_hash
56
+ find_header(%w[X-Authorization-Content-SHA256])
56
57
  end
57
58
 
58
59
  def original_uri
@@ -15,25 +15,26 @@ module ApiAuth
15
15
  @request
16
16
  end
17
17
 
18
- def calculated_md5
18
+ def calculated_hash
19
19
  if @request.body
20
20
  body = @request.body.read
21
21
  @request.body.rewind
22
22
  else
23
23
  body = ''
24
24
  end
25
- md5_base64digest(body)
25
+ sha256_base64digest(body)
26
26
  end
27
27
 
28
- def populate_content_md5
28
+ def populate_content_hash
29
29
  return unless %w[POST PUT].include?(@request.request_method)
30
- @request.env['Content-MD5'] = calculated_md5
30
+
31
+ @request.env['X-Authorization-Content-SHA256'] = calculated_hash
31
32
  fetch_headers
32
33
  end
33
34
 
34
- def md5_mismatch?
35
+ def content_hash_mismatch?
35
36
  if %w[POST PUT].include?(@request.request_method)
36
- calculated_md5 != content_md5
37
+ calculated_hash != content_hash
37
38
  else
38
39
  false
39
40
  end
@@ -51,8 +52,8 @@ module ApiAuth
51
52
  find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
52
53
  end
53
54
 
54
- def content_md5
55
- find_header(%w[CONTENT-MD5 CONTENT_MD5 HTTP-CONTENT-MD5 HTTP_CONTENT_MD5])
55
+ def content_hash
56
+ find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
56
57
  end
57
58
 
58
59
  def original_uri
@@ -18,25 +18,26 @@ module ApiAuth
18
18
  @request
19
19
  end
20
20
 
21
- def calculated_md5
21
+ def calculated_hash
22
22
  if @request.payload
23
23
  body = @request.payload.read
24
24
  @request.payload.instance_variable_get(:@stream).seek(0)
25
25
  else
26
26
  body = ''
27
27
  end
28
- md5_base64digest(body)
28
+ sha256_base64digest(body)
29
29
  end
30
30
 
31
- def populate_content_md5
31
+ def populate_content_hash
32
32
  return unless %w[post put].include?(@request.method.to_s)
33
- @request.headers['Content-MD5'] = calculated_md5
33
+
34
+ @request.headers['X-Authorization-Content-SHA256'] = calculated_hash
34
35
  save_headers
35
36
  end
36
37
 
37
- def md5_mismatch?
38
+ def content_hash_mismatch?
38
39
  if %w[post put].include?(@request.method.to_s)
39
- calculated_md5 != content_md5
40
+ calculated_hash != content_hash
40
41
  else
41
42
  false
42
43
  end
@@ -54,8 +55,8 @@ module ApiAuth
54
55
  find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
55
56
  end
56
57
 
57
- def content_md5
58
- find_header(%w[CONTENT-MD5 CONTENT_MD5])
58
+ def content_hash
59
+ find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
59
60
  end
60
61
 
61
62
  def original_uri
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe 'ApiAuth' do
4
4
  describe 'generating secret keys' do
@@ -36,9 +36,9 @@ describe 'ApiAuth' do
36
36
  ApiAuth.sign!(request, 'abc', '123')
37
37
  end
38
38
 
39
- it 'generates content-md5 header before signing' do
39
+ it 'generates X-Authorization-Content-SHA256 header before signing' do
40
40
  expect(ApiAuth::Headers).to receive(:new).and_return(headers)
41
- expect(headers).to receive(:calculate_md5).ordered
41
+ expect(headers).to receive(:calculate_hash).ordered
42
42
  expect(headers).to receive(:sign_header).ordered
43
43
 
44
44
  ApiAuth.sign!(request, 'abc', '123')
@@ -58,7 +58,7 @@ describe 'ApiAuth' do
58
58
  let(:request) do
59
59
  Net::HTTP::Put.new('/resource.xml?foo=bar&bar=foo',
60
60
  'content-type' => 'text/plain',
61
- 'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
61
+ 'content-hash' => '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=',
62
62
  'date' => Time.now.utc.httpdate)
63
63
  end
64
64
 
@@ -76,7 +76,7 @@ describe 'ApiAuth' do
76
76
  let(:request) do
77
77
  Net::HTTP::Put.new('/resource.xml?foo=bar&bar=foo',
78
78
  'content-type' => 'text/plain',
79
- 'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
79
+ 'X-Authorization-Content-SHA256' => '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=',
80
80
  'date' => Time.now.utc.httpdate)
81
81
  end
82
82
 
@@ -94,8 +94,8 @@ describe 'ApiAuth' do
94
94
  expect(ApiAuth.authentic?(signed_request, '456')).to eq false
95
95
  end
96
96
 
97
- it 'fails to validate non matching md5' do
98
- request['content-md5'] = '12345'
97
+ it 'fails to validate non matching hash' do
98
+ request['X-Authorization-Content-SHA256'] = '12345'
99
99
  expect(ApiAuth.authentic?(signed_request, '123')).to eq false
100
100
  end
101
101
 
@@ -125,7 +125,7 @@ describe 'ApiAuth' do
125
125
  let(:request) do
126
126
  new_request = Net::HTTP::Put.new('/resource.xml?foo=bar&bar=foo',
127
127
  'content-type' => 'text/plain',
128
- 'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==',
128
+ 'X-Authorization-Content-SHA256' => '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=',
129
129
  'date' => Time.now.utc.httpdate)
130
130
  canonical_string = ApiAuth::Headers.new(new_request).canonical_string
131
131
  signature = hmac('123', new_request, canonical_string, 'sha256')
@@ -169,6 +169,13 @@ describe 'ApiAuth' do
169
169
  expect(ApiAuth.authentic?(signed_request, '123', clock_skew: 60.seconds)).to eq false
170
170
  end
171
171
  end
172
+
173
+ context 'when passed the headers_to_sign option' do
174
+ it 'validates the request' do
175
+ request['X-Forwarded-For'] = '192.168.1.1'
176
+ expect(ApiAuth.authentic?(signed_request, '123', headers_to_sign: %w[HTTP_X_FORWARDED_FOR])).to eq true
177
+ end
178
+ end
172
179
  end
173
180
 
174
181
  describe '.access_id' do
data/spec/headers_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe ApiAuth::Headers do
4
4
  describe '#canonical_string' do
@@ -53,7 +53,7 @@ describe ApiAuth::Headers do
53
53
  before do
54
54
  allow(driver).to receive(:http_method).and_return 'GET'
55
55
  allow(driver).to receive(:content_type).and_return 'text/html'
56
- allow(driver).to receive(:content_md5).and_return '12345'
56
+ allow(driver).to receive(:content_hash).and_return '12345'
57
57
  allow(driver).to receive(:request_uri).and_return '/foo'
58
58
  allow(driver).to receive(:timestamp).and_return 'Mon, 23 Jan 1984 03:29:56 GMT'
59
59
  end
@@ -83,7 +83,7 @@ describe ApiAuth::Headers do
83
83
  before do
84
84
  allow(driver).to receive(:http_method).and_return nil
85
85
  allow(driver).to receive(:content_type).and_return 'text/html'
86
- allow(driver).to receive(:content_md5).and_return '12345'
86
+ allow(driver).to receive(:content_hash).and_return '12345'
87
87
  allow(driver).to receive(:request_uri).and_return '/foo'
88
88
  allow(driver).to receive(:timestamp).and_return 'Mon, 23 Jan 1984 03:29:56 GMT'
89
89
  end
@@ -115,7 +115,7 @@ describe ApiAuth::Headers do
115
115
 
116
116
  before do
117
117
  allow(driver).to receive(:content_type).and_return 'text/html'
118
- allow(driver).to receive(:content_md5).and_return '12345'
118
+ allow(driver).to receive(:content_hash).and_return '12345'
119
119
  allow(driver).to receive(:timestamp).and_return 'Mon, 23 Jan 1984 03:29:56 GMT'
120
120
  end
121
121
 
@@ -125,14 +125,40 @@ describe ApiAuth::Headers do
125
125
  end
126
126
  end
127
127
  end
128
+
129
+ context 'when headers to sign are provided' do
130
+ let(:request) do
131
+ Faraday::Request.create('GET') do |req|
132
+ req.options = Faraday::RequestOptions.new(Faraday::FlatParamsEncoder)
133
+ req.params = Faraday::Utils::ParamsHash.new
134
+ req.url('/resource.xml?foo=bar&bar=foo')
135
+ req.headers = { 'X-Forwarded-For' => '192.168.1.1' }
136
+ end
137
+ end
138
+ subject(:headers) { described_class.new(request) }
139
+ let(:driver) { headers.instance_variable_get('@request') }
140
+
141
+ before do
142
+ allow(driver).to receive(:content_type).and_return 'text/html'
143
+ allow(driver).to receive(:content_hash).and_return '12345'
144
+ allow(driver).to receive(:timestamp).and_return 'Mon, 23 Jan 1984 03:29:56 GMT'
145
+ end
146
+
147
+ context 'the driver uses the original_uri' do
148
+ it 'constructs the canonical_string with the original_uri' do
149
+ expect(headers.canonical_string(nil, %w[X-FORWARDED-FOR]))
150
+ .to eq 'GET,text/html,12345,/resource.xml?bar=foo&foo=bar,Mon, 23 Jan 1984 03:29:56 GMT,192.168.1.1'
151
+ end
152
+ end
153
+ end
128
154
  end
129
155
  end
130
156
 
131
- describe '#calculate_md5' do
157
+ describe '#calculate_hash' do
132
158
  subject(:headers) { described_class.new(request) }
133
159
  let(:driver) { headers.instance_variable_get('@request') }
134
160
 
135
- context 'no md5 already calculated' do
161
+ context 'no content hash already calculated' do
136
162
  let(:request) do
137
163
  RestClient::Request.new(
138
164
  url: 'http://google.com',
@@ -141,55 +167,55 @@ describe ApiAuth::Headers do
141
167
  )
142
168
  end
143
169
 
144
- it 'populates the md5 header' do
145
- expect(driver).to receive(:populate_content_md5)
146
- headers.calculate_md5
170
+ it 'populates the content hash header' do
171
+ expect(driver).to receive(:populate_content_hash)
172
+ headers.calculate_hash
147
173
  end
148
174
  end
149
175
 
150
- context 'md5 already calculated' do
176
+ context 'hash already calculated' do
151
177
  let(:request) do
152
178
  RestClient::Request.new(
153
179
  url: 'http://google.com',
154
180
  method: :post,
155
181
  payload: "hello\nworld",
156
- headers: { content_md5: 'abcd' }
182
+ headers: { 'X-Authorization-Content-SHA256' => 'abcd' }
157
183
  )
158
184
  end
159
185
 
160
- it "doesn't populate the md5 header" do
161
- expect(driver).not_to receive(:populate_content_md5)
162
- headers.calculate_md5
186
+ it "doesn't populate the X-Authorization-Content-SHA256 header" do
187
+ expect(driver).not_to receive(:populate_content_hash)
188
+ headers.calculate_hash
163
189
  end
164
190
  end
165
191
  end
166
192
 
167
- describe '#md5_mismatch?' do
193
+ describe '#content_hash_mismatch?' do
168
194
  let(:request) { RestClient::Request.new(url: 'http://google.com', method: :get) }
169
195
  subject(:headers) { described_class.new(request) }
170
196
  let(:driver) { headers.instance_variable_get('@request') }
171
197
 
172
- context 'when request has md5 header' do
198
+ context 'when request has X-Authorization-Content-SHA256 header' do
173
199
  it 'asks the driver' do
174
- allow(driver).to receive(:content_md5).and_return '1234'
200
+ allow(driver).to receive(:content_hash).and_return '1234'
175
201
 
176
- expect(driver).to receive(:md5_mismatch?).and_call_original
177
- headers.md5_mismatch?
202
+ expect(driver).to receive(:content_hash_mismatch?).and_call_original
203
+ headers.content_hash_mismatch?
178
204
  end
179
205
  end
180
206
 
181
- context 'when request has no md5' do
207
+ context 'when request has no content hash' do
182
208
  it "doesn't ask the driver" do
183
- allow(driver).to receive(:content_md5).and_return nil
209
+ allow(driver).to receive(:content_hash).and_return nil
184
210
 
185
- expect(driver).not_to receive(:md5_mismatch?).and_call_original
186
- headers.md5_mismatch?
211
+ expect(driver).not_to receive(:content_hash_mismatch?).and_call_original
212
+ headers.content_hash_mismatch?
187
213
  end
188
214
 
189
215
  it 'returns false' do
190
- allow(driver).to receive(:content_md5).and_return nil
216
+ allow(driver).to receive(:content_hash).and_return nil
191
217
 
192
- expect(headers.md5_mismatch?).to be false
218
+ expect(headers.content_hash_mismatch?).to be false
193
219
  end
194
220
  end
195
221
  end
data/spec/helpers_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe 'ApiAuth::Helpers' do
4
4
  it 'should strip the new line character on a Base64 encoding' do
data/spec/railtie_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe 'Rails integration' do
4
4
  API_KEY_STORE = { '1044' => 'l16imAXie1sRMcJODpOG7UwC1VyoqvO13jejkfpKWX4Z09W8DC9IrU23DvCwMry7pgSFW6c5S1GIfV0OY6F/vUA==' }.freeze
@@ -8,8 +8,8 @@ describe 'Rails integration' do
8
8
  private
9
9
 
10
10
  def require_api_auth
11
- if (access_id = get_api_access_id_from_request)
12
- return true if api_authenticated?(API_KEY_STORE[access_id])
11
+ if (access_id = get_api_access_id_from_request) && api_authenticated?(API_KEY_STORE[access_id])
12
+ return true
13
13
  end
14
14
 
15
15
  respond_to do |format|