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.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -2
- data/.rubocop_todo.yml +51 -9
- data/.travis.yml +9 -25
- data/CHANGELOG.md +36 -0
- data/Gemfile +1 -1
- data/README.md +91 -50
- data/VERSION +1 -1
- data/api_auth.gemspec +7 -5
- data/gemfiles/http4.gemfile +3 -3
- data/gemfiles/rails_52.gemfile +9 -0
- data/gemfiles/rails_60.gemfile +9 -0
- data/gemfiles/rails_61.gemfile +11 -0
- data/lib/api_auth.rb +1 -0
- data/lib/api_auth/base.rb +4 -4
- data/lib/api_auth/headers.rb +22 -11
- data/lib/api_auth/helpers.rb +2 -2
- data/lib/api_auth/railtie.rb +13 -5
- data/lib/api_auth/request_drivers/action_controller.rb +9 -8
- data/lib/api_auth/request_drivers/curb.rb +4 -4
- data/lib/api_auth/request_drivers/faraday.rb +13 -12
- data/lib/api_auth/request_drivers/grape_request.rb +87 -0
- data/lib/api_auth/request_drivers/http.rb +13 -8
- data/lib/api_auth/request_drivers/httpi.rb +9 -8
- data/lib/api_auth/request_drivers/net_http.rb +9 -8
- data/lib/api_auth/request_drivers/rack.rb +9 -8
- data/lib/api_auth/request_drivers/rest_client.rb +9 -8
- data/spec/api_auth_spec.rb +15 -8
- data/spec/headers_spec.rb +51 -25
- data/spec/helpers_spec.rb +1 -1
- data/spec/railtie_spec.rb +3 -3
- data/spec/request_drivers/action_controller_spec.rb +45 -39
- data/spec/request_drivers/action_dispatch_spec.rb +51 -45
- data/spec/request_drivers/curb_spec.rb +16 -10
- data/spec/request_drivers/faraday_spec.rb +49 -43
- data/spec/request_drivers/grape_request_spec.rb +280 -0
- data/spec/request_drivers/http_spec.rb +29 -23
- data/spec/request_drivers/httpi_spec.rb +28 -22
- data/spec/request_drivers/net_http_spec.rb +29 -23
- data/spec/request_drivers/rack_spec.rb +41 -35
- data/spec/request_drivers/rest_client_spec.rb +42 -36
- data/spec/spec_helper.rb +2 -1
- metadata +51 -26
- data/gemfiles/http2.gemfile +0 -7
- data/gemfiles/http3.gemfile +0 -7
- data/gemfiles/rails_4.gemfile +0 -11
- data/gemfiles/rails_41.gemfile +0 -11
- data/gemfiles/rails_42.gemfile +0 -11
- data/gemfiles/rails_5.gemfile +0 -11
- data/gemfiles/rails_51.gemfile +0 -9
- data/spec/.rubocop.yml +0 -5
@@ -12,18 +12,19 @@ module ApiAuth
|
|
12
12
|
@request
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
|
15
|
+
def calculated_hash
|
16
|
+
sha256_base64digest(body)
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def populate_content_hash
|
20
20
|
return unless %w[POST PUT].include?(http_method)
|
21
|
-
|
21
|
+
|
22
|
+
@request['X-Authorization-Content-SHA256'] = calculated_hash
|
22
23
|
end
|
23
24
|
|
24
|
-
def
|
25
|
+
def content_hash_mismatch?
|
25
26
|
if %w[POST PUT].include?(http_method)
|
26
|
-
|
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
|
41
|
-
find_header(%w[CONTENT-
|
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
|
19
|
-
|
18
|
+
def calculated_hash
|
19
|
+
sha256_base64digest(@request.body || '')
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
22
|
+
def populate_content_hash
|
23
23
|
return unless @request.body
|
24
|
-
|
24
|
+
|
25
|
+
@request.headers['X-Authorization-Content-SHA256'] = calculated_hash
|
25
26
|
fetch_headers
|
26
27
|
end
|
27
28
|
|
28
|
-
def
|
29
|
+
def content_hash_mismatch?
|
29
30
|
if @request.body
|
30
|
-
|
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
|
49
|
-
find_header(%w[CONTENT-
|
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
|
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
|
-
|
26
|
+
sha256_base64digest(body || '')
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
29
|
+
def populate_content_hash
|
30
30
|
return unless @request.class::REQUEST_HAS_BODY
|
31
|
-
|
31
|
+
|
32
|
+
@request['X-Authorization-Content-SHA256'] = calculated_hash
|
32
33
|
end
|
33
34
|
|
34
|
-
def
|
35
|
+
def content_hash_mismatch?
|
35
36
|
if @request.class::REQUEST_HAS_BODY
|
36
|
-
|
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
|
55
|
-
find_header(%w[
|
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
|
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
|
-
|
25
|
+
sha256_base64digest(body)
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
28
|
+
def populate_content_hash
|
29
29
|
return unless %w[POST PUT].include?(@request.request_method)
|
30
|
-
|
30
|
+
|
31
|
+
@request.env['X-Authorization-Content-SHA256'] = calculated_hash
|
31
32
|
fetch_headers
|
32
33
|
end
|
33
34
|
|
34
|
-
def
|
35
|
+
def content_hash_mismatch?
|
35
36
|
if %w[POST PUT].include?(@request.request_method)
|
36
|
-
|
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
|
55
|
-
find_header(%w[
|
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
|
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
|
-
|
28
|
+
sha256_base64digest(body)
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
31
|
+
def populate_content_hash
|
32
32
|
return unless %w[post put].include?(@request.method.to_s)
|
33
|
-
|
33
|
+
|
34
|
+
@request.headers['X-Authorization-Content-SHA256'] = calculated_hash
|
34
35
|
save_headers
|
35
36
|
end
|
36
37
|
|
37
|
-
def
|
38
|
+
def content_hash_mismatch?
|
38
39
|
if %w[post put].include?(@request.method.to_s)
|
39
|
-
|
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
|
58
|
-
find_header(%w[CONTENT-
|
58
|
+
def content_hash
|
59
|
+
find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
|
59
60
|
end
|
60
61
|
|
61
62
|
def original_uri
|
data/spec/api_auth_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
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
|
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(:
|
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-
|
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
|
-
'
|
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
|
98
|
-
request['
|
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
|
-
'
|
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
|
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(:
|
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(:
|
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(:
|
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 '#
|
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
|
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
|
145
|
-
expect(driver).to receive(:
|
146
|
-
headers.
|
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 '
|
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: {
|
182
|
+
headers: { 'X-Authorization-Content-SHA256' => 'abcd' }
|
157
183
|
)
|
158
184
|
end
|
159
185
|
|
160
|
-
it "doesn't populate the
|
161
|
-
expect(driver).not_to receive(:
|
162
|
-
headers.
|
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 '#
|
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
|
198
|
+
context 'when request has X-Authorization-Content-SHA256 header' do
|
173
199
|
it 'asks the driver' do
|
174
|
-
allow(driver).to receive(:
|
200
|
+
allow(driver).to receive(:content_hash).and_return '1234'
|
175
201
|
|
176
|
-
expect(driver).to receive(:
|
177
|
-
headers.
|
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
|
207
|
+
context 'when request has no content hash' do
|
182
208
|
it "doesn't ask the driver" do
|
183
|
-
allow(driver).to receive(:
|
209
|
+
allow(driver).to receive(:content_hash).and_return nil
|
184
210
|
|
185
|
-
expect(driver).not_to receive(:
|
186
|
-
headers.
|
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(:
|
216
|
+
allow(driver).to receive(:content_hash).and_return nil
|
191
217
|
|
192
|
-
expect(headers.
|
218
|
+
expect(headers.content_hash_mismatch?).to be false
|
193
219
|
end
|
194
220
|
end
|
195
221
|
end
|
data/spec/helpers_spec.rb
CHANGED
data/spec/railtie_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
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
|
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|
|