api-auth 2.5.0 → 2.6.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/.github/workflows/main.yml +71 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +14 -1
- data/.rubocop_todo.yml +11 -30
- data/Appraisals +12 -18
- data/CHANGELOG.md +18 -0
- data/Gemfile +0 -2
- data/README.md +25 -16
- data/VERSION +1 -1
- data/api_auth.gemspec +14 -7
- data/gemfiles/rails_60.gemfile +5 -5
- data/gemfiles/rails_61.gemfile +5 -7
- data/gemfiles/rails_70.gemfile +9 -0
- data/lib/api_auth/base.rb +4 -4
- data/lib/api_auth/headers.rb +6 -4
- data/lib/api_auth/helpers.rb +4 -0
- data/lib/api_auth/railtie.rb +7 -7
- data/lib/api_auth/request_drivers/action_controller.rb +9 -4
- data/lib/api_auth/request_drivers/faraday_env.rb +102 -0
- data/lib/api_auth.rb +1 -0
- data/lib/faraday/api_auth/middleware.rb +35 -0
- data/lib/faraday/api_auth.rb +8 -0
- data/spec/api_auth_spec.rb +3 -3
- data/spec/faraday_middleware_spec.rb +17 -0
- data/spec/headers_spec.rb +8 -8
- data/spec/railtie_spec.rb +2 -1
- data/spec/request_drivers/action_controller_spec.rb +42 -3
- data/spec/request_drivers/action_dispatch_spec.rb +132 -8
- data/spec/request_drivers/faraday_env_spec.rb +188 -0
- data/spec/request_drivers/http_spec.rb +1 -1
- data/spec/request_drivers/httpi_spec.rb +2 -2
- data/spec/request_drivers/rest_client_spec.rb +19 -19
- data/spec/spec_helper.rb +6 -2
- metadata +70 -61
- data/.travis.yml +0 -30
- data/gemfiles/http4.gemfile +0 -7
- data/gemfiles/rails_52.gemfile +0 -9
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module ApiAuth
|
|
2
|
+
module RequestDrivers # :nodoc:
|
|
3
|
+
# Internally, Faraday uses the class Faraday::Env to represent requests. The class is not meant
|
|
4
|
+
# to be directly exposed to users, but this is what Faraday middlewares work with. See
|
|
5
|
+
# <https://lostisland.github.io/faraday/middleware/>.
|
|
6
|
+
class FaradayEnv
|
|
7
|
+
include ApiAuth::Helpers
|
|
8
|
+
|
|
9
|
+
def initialize(env)
|
|
10
|
+
@env = env
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def set_auth_header(header)
|
|
14
|
+
@env.request_headers['Authorization'] = header
|
|
15
|
+
@env
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def calculated_hash
|
|
19
|
+
sha256_base64digest(body)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def populate_content_hash
|
|
23
|
+
return unless %w[POST PUT PATCH].include?(http_method)
|
|
24
|
+
|
|
25
|
+
@env.request_headers['X-Authorization-Content-SHA256'] = calculated_hash
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def content_hash_mismatch?
|
|
29
|
+
if %w[POST PUT PATCH].include?(http_method)
|
|
30
|
+
calculated_hash != content_hash
|
|
31
|
+
else
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def http_method
|
|
37
|
+
@env.method.to_s.upcase
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def content_type
|
|
41
|
+
type = find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
|
|
42
|
+
|
|
43
|
+
# When sending a body-less POST request, the Content-Type is set at the last minute by the
|
|
44
|
+
# Net::HTTP adapter, which states in the documentation for Net::HTTP#post:
|
|
45
|
+
#
|
|
46
|
+
# > You should set Content-Type: header field for POST. If no Content-Type: field given,
|
|
47
|
+
# > this method uses “application/x-www-form-urlencoded” by default.
|
|
48
|
+
#
|
|
49
|
+
# The same applies to PATCH and PUT. Hopefully the other HTTP adapters behave similarly.
|
|
50
|
+
#
|
|
51
|
+
type ||= 'application/x-www-form-urlencoded' if %w[POST PATCH PUT].include?(http_method)
|
|
52
|
+
|
|
53
|
+
type
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def content_hash
|
|
57
|
+
find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def original_uri
|
|
61
|
+
find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def request_uri
|
|
65
|
+
@env.url.request_uri
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def set_date
|
|
69
|
+
@env.request_headers['Date'] = Time.now.utc.httpdate
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def timestamp
|
|
73
|
+
find_header(%w[DATE HTTP_DATE])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def authorization_header
|
|
77
|
+
find_header(%w[Authorization AUTHORIZATION HTTP_AUTHORIZATION])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def body
|
|
81
|
+
body_source = @env.request_body
|
|
82
|
+
if body_source.respond_to?(:read)
|
|
83
|
+
result = body_source.read
|
|
84
|
+
body_source.rewind
|
|
85
|
+
result
|
|
86
|
+
else
|
|
87
|
+
body_source.to_s
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def fetch_headers
|
|
92
|
+
capitalize_keys @env.request_headers
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def find_header(keys)
|
|
98
|
+
keys.map { |key| @env.request_headers[key] }.compact.first
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/api_auth.rb
CHANGED
|
@@ -14,6 +14,7 @@ require 'api_auth/request_drivers/action_dispatch'
|
|
|
14
14
|
require 'api_auth/request_drivers/rack'
|
|
15
15
|
require 'api_auth/request_drivers/httpi'
|
|
16
16
|
require 'api_auth/request_drivers/faraday'
|
|
17
|
+
require 'api_auth/request_drivers/faraday_env'
|
|
17
18
|
require 'api_auth/request_drivers/http'
|
|
18
19
|
|
|
19
20
|
require 'api_auth/headers'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'api_auth'
|
|
2
|
+
|
|
3
|
+
module Faraday
|
|
4
|
+
module ApiAuth
|
|
5
|
+
# Request middleware for Faraday. It takes the same arguments as ApiAuth.sign!.
|
|
6
|
+
#
|
|
7
|
+
# You will usually need to include it after the other middlewares since ApiAuth needs to hash
|
|
8
|
+
# the final request.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
#
|
|
12
|
+
# ```ruby
|
|
13
|
+
# require 'faraday/api_auth'
|
|
14
|
+
#
|
|
15
|
+
# conn = Faraday.new do |f|
|
|
16
|
+
# f.request :api_auth, access_id, secret_key
|
|
17
|
+
# # Alternatively:
|
|
18
|
+
# # f.use Faraday::ApiAuth::Middleware, access_id, secret_key
|
|
19
|
+
# end
|
|
20
|
+
# ```
|
|
21
|
+
#
|
|
22
|
+
class Middleware < Faraday::Middleware
|
|
23
|
+
def initialize(app, access_id, secret_key, options = {})
|
|
24
|
+
super(app)
|
|
25
|
+
@access_id = access_id
|
|
26
|
+
@secret_key = secret_key
|
|
27
|
+
@options = options
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def on_request(env)
|
|
31
|
+
::ApiAuth.sign!(env, @access_id, @secret_key, @options)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/spec/api_auth_spec.rb
CHANGED
|
@@ -24,7 +24,7 @@ describe 'ApiAuth' do
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
describe '.sign!' do
|
|
27
|
-
let(:request) { RestClient::Request.new(url: '
|
|
27
|
+
let(:request) { RestClient::Request.new(url: 'https://google.com', method: :get) }
|
|
28
28
|
let(:headers) { ApiAuth::Headers.new(request) }
|
|
29
29
|
|
|
30
30
|
it 'generates date header before signing' do
|
|
@@ -182,7 +182,7 @@ describe 'ApiAuth' do
|
|
|
182
182
|
context 'normal APIAuth Auth header' do
|
|
183
183
|
let(:request) do
|
|
184
184
|
RestClient::Request.new(
|
|
185
|
-
url: '
|
|
185
|
+
url: 'https://google.com',
|
|
186
186
|
method: :get,
|
|
187
187
|
headers: { authorization: 'APIAuth 1044:aGVsbG8gd29ybGQ=' }
|
|
188
188
|
)
|
|
@@ -196,7 +196,7 @@ describe 'ApiAuth' do
|
|
|
196
196
|
context 'Corporate prefixed APIAuth header' do
|
|
197
197
|
let(:request) do
|
|
198
198
|
RestClient::Request.new(
|
|
199
|
-
url: '
|
|
199
|
+
url: 'https://google.com',
|
|
200
200
|
method: :get,
|
|
201
201
|
headers: { authorization: 'Corporate APIAuth 1044:aGVsbG8gd29ybGQ=' }
|
|
202
202
|
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'faraday/api_auth'
|
|
3
|
+
|
|
4
|
+
describe Faraday::ApiAuth::Middleware do
|
|
5
|
+
it 'adds the Authorization headers' do
|
|
6
|
+
conn = Faraday.new('http://localhost/') do |f|
|
|
7
|
+
f.request :api_auth, 'foo', 'secret', digest: 'sha256'
|
|
8
|
+
f.adapter :test do |stub|
|
|
9
|
+
stub.get('http://localhost/test') do |env|
|
|
10
|
+
[200, {}, env.request_headers['Authorization']]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
response = conn.get('test', nil, { 'Date' => 'Tue, 02 Aug 2022 09:29:24 GMT' })
|
|
15
|
+
expect(response.body).to eq 'APIAuth-HMAC-SHA256 foo:Tn/lIZ9kphcO32DwG4wFHenqBt37miDEIkA5ykLgGiQ='
|
|
16
|
+
end
|
|
17
|
+
end
|
data/spec/headers_spec.rb
CHANGED
|
@@ -8,7 +8,7 @@ describe ApiAuth::Headers do
|
|
|
8
8
|
let(:uri) { '' }
|
|
9
9
|
|
|
10
10
|
context 'uri with just host without /' do
|
|
11
|
-
let(:uri) { '
|
|
11
|
+
let(:uri) { 'https://google.com'.freeze }
|
|
12
12
|
|
|
13
13
|
it 'return / as canonical string path' do
|
|
14
14
|
expect(subject.canonical_string).to eq('GET,,,/,')
|
|
@@ -20,7 +20,7 @@ describe ApiAuth::Headers do
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
context 'uri with host and /' do
|
|
23
|
-
let(:uri) { '
|
|
23
|
+
let(:uri) { 'https://google.com/'.freeze }
|
|
24
24
|
|
|
25
25
|
it 'return / as canonical string path' do
|
|
26
26
|
expect(subject.canonical_string).to eq('GET,,,/,')
|
|
@@ -31,8 +31,8 @@ describe ApiAuth::Headers do
|
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
context 'uri has a string matching
|
|
35
|
-
let(:uri) { '
|
|
34
|
+
context 'uri has a string matching https:// in it' do
|
|
35
|
+
let(:uri) { 'https://google.com/?redirect_to=https://www.example.com'.freeze }
|
|
36
36
|
|
|
37
37
|
it 'return /?redirect_to=https://www.example.com as canonical string path' do
|
|
38
38
|
expect(subject.canonical_string).to eq('GET,,,/?redirect_to=https://www.example.com,')
|
|
@@ -46,7 +46,7 @@ describe ApiAuth::Headers do
|
|
|
46
46
|
|
|
47
47
|
context 'string construction' do
|
|
48
48
|
context 'with a driver that supplies http_method' do
|
|
49
|
-
let(:request) { RestClient::Request.new(url: '
|
|
49
|
+
let(:request) { RestClient::Request.new(url: 'https://google.com', method: :get) }
|
|
50
50
|
subject(:headers) { described_class.new(request) }
|
|
51
51
|
let(:driver) { headers.instance_variable_get('@request') }
|
|
52
52
|
|
|
@@ -161,7 +161,7 @@ describe ApiAuth::Headers do
|
|
|
161
161
|
context 'no content hash already calculated' do
|
|
162
162
|
let(:request) do
|
|
163
163
|
RestClient::Request.new(
|
|
164
|
-
url: '
|
|
164
|
+
url: 'https://google.com',
|
|
165
165
|
method: :post,
|
|
166
166
|
payload: "hello\nworld"
|
|
167
167
|
)
|
|
@@ -176,7 +176,7 @@ describe ApiAuth::Headers do
|
|
|
176
176
|
context 'hash already calculated' do
|
|
177
177
|
let(:request) do
|
|
178
178
|
RestClient::Request.new(
|
|
179
|
-
url: '
|
|
179
|
+
url: 'https://google.com',
|
|
180
180
|
method: :post,
|
|
181
181
|
payload: "hello\nworld",
|
|
182
182
|
headers: { 'X-Authorization-Content-SHA256' => 'abcd' }
|
|
@@ -191,7 +191,7 @@ describe ApiAuth::Headers do
|
|
|
191
191
|
end
|
|
192
192
|
|
|
193
193
|
describe '#content_hash_mismatch?' do
|
|
194
|
-
let(:request) { RestClient::Request.new(url: '
|
|
194
|
+
let(:request) { RestClient::Request.new(url: 'https://google.com', method: :get) }
|
|
195
195
|
subject(:headers) { described_class.new(request) }
|
|
196
196
|
let(:driver) { headers.instance_variable_get('@request') }
|
|
197
197
|
|
data/spec/railtie_spec.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
|
+
require 'action_controller/test_case'
|
|
2
3
|
|
|
3
4
|
describe 'Rails integration' do
|
|
4
5
|
API_KEY_STORE = { '1044' => 'l16imAXie1sRMcJODpOG7UwC1VyoqvO13jejkfpKWX4Z09W8DC9IrU23DvCwMry7pgSFW6c5S1GIfV0OY6F/vUA==' }.freeze
|
|
@@ -116,7 +117,7 @@ describe 'Rails integration' do
|
|
|
116
117
|
describe 'Rails ActiveResource integration' do
|
|
117
118
|
class TestResource < ActiveResource::Base
|
|
118
119
|
with_api_auth '1044', API_KEY_STORE['1044']
|
|
119
|
-
self.site = '
|
|
120
|
+
self.site = 'https://localhost/'
|
|
120
121
|
self.format = :xml
|
|
121
122
|
end
|
|
122
123
|
|
|
@@ -4,6 +4,7 @@ if defined?(ActionController::Request)
|
|
|
4
4
|
|
|
5
5
|
describe ApiAuth::RequestDrivers::ActionControllerRequest do
|
|
6
6
|
let(:timestamp) { Time.now.utc.httpdate }
|
|
7
|
+
let(:content_sha256) { '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' }
|
|
7
8
|
|
|
8
9
|
let(:request) do
|
|
9
10
|
ActionController::Request.new(
|
|
@@ -11,7 +12,35 @@ if defined?(ActionController::Request)
|
|
|
11
12
|
'PATH_INFO' => '/resource.xml',
|
|
12
13
|
'QUERY_STRING' => 'foo=bar&bar=foo',
|
|
13
14
|
'REQUEST_METHOD' => 'PUT',
|
|
14
|
-
'
|
|
15
|
+
'HTTP_X_AUTHORIZATION_CONTENT_SHA256' => content_sha256,
|
|
16
|
+
'CONTENT_TYPE' => 'text/plain',
|
|
17
|
+
'CONTENT_LENGTH' => '11',
|
|
18
|
+
'HTTP_DATE' => timestamp,
|
|
19
|
+
'rack.input' => StringIO.new("hello\nworld")
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
let(:request2) do
|
|
24
|
+
ActionController::Request.new(
|
|
25
|
+
'AUTHORIZATION' => 'APIAuth 1044:12345',
|
|
26
|
+
'PATH_INFO' => '/resource.xml',
|
|
27
|
+
'QUERY_STRING' => 'foo=bar&bar=foo',
|
|
28
|
+
'REQUEST_METHOD' => 'PUT',
|
|
29
|
+
'X_AUTHORIZATION_CONTENT_SHA256' => content_sha256,
|
|
30
|
+
'CONTENT_TYPE' => 'text/plain',
|
|
31
|
+
'CONTENT_LENGTH' => '11',
|
|
32
|
+
'HTTP_DATE' => timestamp,
|
|
33
|
+
'rack.input' => StringIO.new("hello\nworld")
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
let(:request3) do
|
|
38
|
+
ActionController::Request.new(
|
|
39
|
+
'AUTHORIZATION' => 'APIAuth 1044:12345',
|
|
40
|
+
'PATH_INFO' => '/resource.xml',
|
|
41
|
+
'QUERY_STRING' => 'foo=bar&bar=foo',
|
|
42
|
+
'REQUEST_METHOD' => 'PUT',
|
|
43
|
+
'X-AUTHORIZATION-CONTENT-SHA256' => content_sha256,
|
|
15
44
|
'CONTENT_TYPE' => 'text/plain',
|
|
16
45
|
'CONTENT_LENGTH' => '11',
|
|
17
46
|
'HTTP_DATE' => timestamp,
|
|
@@ -27,7 +56,17 @@ if defined?(ActionController::Request)
|
|
|
27
56
|
end
|
|
28
57
|
|
|
29
58
|
it 'gets the content_hash' do
|
|
30
|
-
expect(driven_request.content_hash).to eq(
|
|
59
|
+
expect(driven_request.content_hash).to eq(content_sha256)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'gets the content_hash for request 2' do
|
|
63
|
+
example_request = ApiAuth::RequestDrivers::ActionControllerRequest.new(request2)
|
|
64
|
+
expect(example_request.content_hash).to eq(content_sha256)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'gets the content_hash for request 3' do
|
|
68
|
+
example_request = ApiAuth::RequestDrivers::ActionControllerRequest.new(request3)
|
|
69
|
+
expect(example_request.content_hash).to eq(content_sha256)
|
|
31
70
|
end
|
|
32
71
|
|
|
33
72
|
it 'gets the request_uri' do
|
|
@@ -50,7 +89,7 @@ if defined?(ActionController::Request)
|
|
|
50
89
|
it 'treats no body as empty string' do
|
|
51
90
|
request.env['rack.input'] = StringIO.new
|
|
52
91
|
request.env['CONTENT_LENGTH'] = 0
|
|
53
|
-
expect(driven_request.calculated_hash).to eq(
|
|
92
|
+
expect(driven_request.calculated_hash).to eq(content_sha256)
|
|
54
93
|
end
|
|
55
94
|
end
|
|
56
95
|
|
|
@@ -4,6 +4,8 @@ if defined?(ActionDispatch::Request)
|
|
|
4
4
|
|
|
5
5
|
describe ApiAuth::RequestDrivers::ActionDispatchRequest do
|
|
6
6
|
let(:timestamp) { Time.now.utc.httpdate }
|
|
7
|
+
let(:content_sha256) { '47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' }
|
|
8
|
+
let(:content_md5) { '+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' }
|
|
7
9
|
|
|
8
10
|
let(:request) do
|
|
9
11
|
ActionDispatch::Request.new(
|
|
@@ -11,7 +13,49 @@ if defined?(ActionDispatch::Request)
|
|
|
11
13
|
'PATH_INFO' => '/resource.xml',
|
|
12
14
|
'QUERY_STRING' => 'foo=bar&bar=foo',
|
|
13
15
|
'REQUEST_METHOD' => 'PUT',
|
|
14
|
-
'
|
|
16
|
+
'HTTP_X_AUTHORIZATION_CONTENT_SHA256' => content_sha256,
|
|
17
|
+
'CONTENT_TYPE' => 'text/plain',
|
|
18
|
+
'CONTENT_LENGTH' => '11',
|
|
19
|
+
'HTTP_DATE' => timestamp,
|
|
20
|
+
'rack.input' => StringIO.new("hello\nworld")
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
let(:request2) do
|
|
25
|
+
ActionDispatch::Request.new(
|
|
26
|
+
'AUTHORIZATION' => 'APIAuth 1044:12345',
|
|
27
|
+
'PATH_INFO' => '/resource.xml',
|
|
28
|
+
'QUERY_STRING' => 'foo=bar&bar=foo',
|
|
29
|
+
'REQUEST_METHOD' => 'PUT',
|
|
30
|
+
'X_AUTHORIZATION_CONTENT_SHA256' => content_sha256,
|
|
31
|
+
'CONTENT_TYPE' => 'text/plain',
|
|
32
|
+
'CONTENT_LENGTH' => '11',
|
|
33
|
+
'HTTP_DATE' => timestamp,
|
|
34
|
+
'rack.input' => StringIO.new("hello\nworld")
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
let(:request3) do
|
|
39
|
+
ActionDispatch::Request.new(
|
|
40
|
+
'AUTHORIZATION' => 'APIAuth 1044:12345',
|
|
41
|
+
'PATH_INFO' => '/resource.xml',
|
|
42
|
+
'QUERY_STRING' => 'foo=bar&bar=foo',
|
|
43
|
+
'REQUEST_METHOD' => 'PUT',
|
|
44
|
+
'X-AUTHORIZATION-CONTENT-SHA256' => content_sha256,
|
|
45
|
+
'CONTENT_TYPE' => 'text/plain',
|
|
46
|
+
'CONTENT_LENGTH' => '11',
|
|
47
|
+
'HTTP_DATE' => timestamp,
|
|
48
|
+
'rack.input' => StringIO.new("hello\nworld")
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
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,
|
|
15
59
|
'CONTENT_TYPE' => 'text/plain',
|
|
16
60
|
'CONTENT_LENGTH' => '11',
|
|
17
61
|
'HTTP_DATE' => timestamp,
|
|
@@ -20,6 +64,11 @@ if defined?(ActionDispatch::Request)
|
|
|
20
64
|
end
|
|
21
65
|
|
|
22
66
|
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) }
|
|
23
72
|
|
|
24
73
|
describe 'getting headers correctly' do
|
|
25
74
|
it 'gets the content_type' do
|
|
@@ -27,7 +76,22 @@ if defined?(ActionDispatch::Request)
|
|
|
27
76
|
end
|
|
28
77
|
|
|
29
78
|
it 'gets the content_hash' do
|
|
30
|
-
expect(driven_request.content_hash).to eq(
|
|
79
|
+
expect(driven_request.content_hash).to eq(content_sha256)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'gets the content_hash for request 2' do
|
|
83
|
+
example_request = ApiAuth::RequestDrivers::ActionDispatchRequest.new(request2)
|
|
84
|
+
expect(example_request.content_hash).to eq(content_sha256)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'gets the content_hash for request 3' do
|
|
88
|
+
example_request = ApiAuth::RequestDrivers::ActionDispatchRequest.new(request3)
|
|
89
|
+
expect(example_request.content_hash).to eq(content_sha256)
|
|
90
|
+
end
|
|
91
|
+
|
|
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)
|
|
31
95
|
end
|
|
32
96
|
|
|
33
97
|
it 'gets the request_uri' do
|
|
@@ -44,13 +108,17 @@ if defined?(ActionDispatch::Request)
|
|
|
44
108
|
|
|
45
109
|
describe '#calculated_hash' do
|
|
46
110
|
it 'calculates hash from the body' do
|
|
47
|
-
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==])
|
|
48
116
|
end
|
|
49
117
|
|
|
50
118
|
it 'treats no body as empty string' do
|
|
51
119
|
request.env['rack.input'] = StringIO.new
|
|
52
120
|
request.env['CONTENT_LENGTH'] = 0
|
|
53
|
-
expect(driven_request.calculated_hash).to eq('47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=')
|
|
121
|
+
expect(driven_request.calculated_hash).to eq(['47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='])
|
|
54
122
|
end
|
|
55
123
|
end
|
|
56
124
|
|
|
@@ -102,12 +170,12 @@ if defined?(ActionDispatch::Request)
|
|
|
102
170
|
it 'populates content hash' do
|
|
103
171
|
request.env['REQUEST_METHOD'] = 'POST'
|
|
104
172
|
driven_request.populate_content_hash
|
|
105
|
-
expect(request.env['X-AUTHORIZATION-CONTENT-SHA256']).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
|
|
173
|
+
expect(request.env['X-AUTHORIZATION-CONTENT-SHA256']).to eq(['JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='])
|
|
106
174
|
end
|
|
107
175
|
|
|
108
176
|
it 'refreshes the cached headers' do
|
|
109
177
|
driven_request.populate_content_hash
|
|
110
|
-
expect(driven_request.content_hash).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
|
|
178
|
+
expect(driven_request.content_hash).to eq(['JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='])
|
|
111
179
|
end
|
|
112
180
|
end
|
|
113
181
|
|
|
@@ -115,12 +183,12 @@ if defined?(ActionDispatch::Request)
|
|
|
115
183
|
it 'populates content hash' do
|
|
116
184
|
request.env['REQUEST_METHOD'] = 'PUT'
|
|
117
185
|
driven_request.populate_content_hash
|
|
118
|
-
expect(request.env['X-AUTHORIZATION-CONTENT-SHA256']).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
|
|
186
|
+
expect(request.env['X-AUTHORIZATION-CONTENT-SHA256']).to eq(['JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='])
|
|
119
187
|
end
|
|
120
188
|
|
|
121
189
|
it 'refreshes the cached headers' do
|
|
122
190
|
driven_request.populate_content_hash
|
|
123
|
-
expect(driven_request.content_hash).to eq('JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g=')
|
|
191
|
+
expect(driven_request.content_hash).to eq(['JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='])
|
|
124
192
|
end
|
|
125
193
|
end
|
|
126
194
|
|
|
@@ -161,73 +229,129 @@ if defined?(ActionDispatch::Request)
|
|
|
161
229
|
context 'when getting' do
|
|
162
230
|
before do
|
|
163
231
|
request.env['REQUEST_METHOD'] = 'GET'
|
|
232
|
+
request_md5.env['REQUEST_METHOD'] = 'GET'
|
|
164
233
|
end
|
|
165
234
|
|
|
166
235
|
it 'is false' do
|
|
167
236
|
expect(driven_request.content_hash_mismatch?).to be false
|
|
168
237
|
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
|
|
169
246
|
end
|
|
170
247
|
|
|
171
248
|
context 'when posting' do
|
|
172
249
|
before do
|
|
173
250
|
request.env['REQUEST_METHOD'] = 'POST'
|
|
251
|
+
request_md5.env['REQUEST_METHOD'] = 'POST'
|
|
174
252
|
end
|
|
175
253
|
|
|
176
254
|
context 'when calculated matches sent' do
|
|
177
255
|
before do
|
|
178
256
|
request.env['X-AUTHORIZATION-CONTENT-SHA256'] = 'JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='
|
|
257
|
+
request_md5.env['CONTENT_MD5'] = 'kZXQvrKoieG+Be1rsZVINw=='
|
|
179
258
|
end
|
|
180
259
|
|
|
181
260
|
it 'is false' do
|
|
182
261
|
expect(driven_request.content_hash_mismatch?).to be false
|
|
183
262
|
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
|
|
184
271
|
end
|
|
185
272
|
|
|
186
273
|
context "when calculated doesn't match sent" do
|
|
187
274
|
before do
|
|
188
275
|
request.env['X-AUTHORIZATION-CONTENT-SHA256'] = '3'
|
|
276
|
+
request_md5.env['CONTENT_MD5'] = '3'
|
|
189
277
|
end
|
|
190
278
|
|
|
191
279
|
it 'is true' do
|
|
192
280
|
expect(driven_request.content_hash_mismatch?).to be true
|
|
193
281
|
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
|
|
194
290
|
end
|
|
195
291
|
end
|
|
196
292
|
|
|
197
293
|
context 'when putting' do
|
|
198
294
|
before do
|
|
199
295
|
request.env['REQUEST_METHOD'] = 'PUT'
|
|
296
|
+
request_md5.env['REQUEST_METHOD'] = 'PUT'
|
|
200
297
|
end
|
|
201
298
|
|
|
202
299
|
context 'when calculated matches sent' do
|
|
203
300
|
before do
|
|
204
301
|
request.env['X-AUTHORIZATION-CONTENT-SHA256'] = 'JsYKYdAdtYNspw/v1EpqAWYgQTyO9fJZpsVhLU9507g='
|
|
302
|
+
request_md5.env['CONTENT_MD5'] = 'kZXQvrKoieG+Be1rsZVINw=='
|
|
205
303
|
end
|
|
206
304
|
|
|
207
305
|
it 'is false' do
|
|
208
306
|
expect(driven_request.content_hash_mismatch?).to be false
|
|
209
307
|
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
|
|
210
316
|
end
|
|
211
317
|
|
|
212
318
|
context "when calculated doesn't match sent" do
|
|
213
319
|
before do
|
|
214
320
|
request.env['X-AUTHORIZATION-CONTENT-SHA256'] = '3'
|
|
321
|
+
request_md5.env['CONTENT_MD5'] = '3'
|
|
215
322
|
end
|
|
216
323
|
|
|
217
324
|
it 'is true' do
|
|
218
325
|
expect(driven_request.content_hash_mismatch?).to be true
|
|
219
326
|
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
|
|
220
335
|
end
|
|
221
336
|
end
|
|
222
337
|
|
|
223
338
|
context 'when deleting' do
|
|
224
339
|
before do
|
|
225
340
|
request.env['REQUEST_METHOD'] = 'DELETE'
|
|
341
|
+
request_md5.env['REQUEST_METHOD'] = 'DELETE'
|
|
226
342
|
end
|
|
227
343
|
|
|
228
344
|
it 'is false' do
|
|
229
345
|
expect(driven_request.content_hash_mismatch?).to be false
|
|
230
346
|
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
|
|
231
355
|
end
|
|
232
356
|
end
|
|
233
357
|
|