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
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.5.0
|
data/api_auth.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
$LOAD_PATH.push File.expand_path('
|
1
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'api-auth'
|
@@ -9,22 +9,24 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.authors = ['Mauricio Gomes']
|
10
10
|
s.email = 'mauricio@edge14.com'
|
11
11
|
|
12
|
-
s.required_ruby_version = '>= 2.
|
12
|
+
s.required_ruby_version = '>= 2.5.0'
|
13
13
|
|
14
|
-
s.add_development_dependency 'actionpack', '< 6.
|
14
|
+
s.add_development_dependency 'actionpack', '< 6.2', '> 5.0'
|
15
15
|
s.add_development_dependency 'activeresource', '>= 4.0'
|
16
|
-
s.add_development_dependency 'activesupport', '< 6.
|
16
|
+
s.add_development_dependency 'activesupport', '< 6.2', '> 5.0'
|
17
17
|
s.add_development_dependency 'amatch'
|
18
18
|
s.add_development_dependency 'appraisal'
|
19
19
|
s.add_development_dependency 'curb', '~> 0.8'
|
20
|
-
s.add_development_dependency 'faraday', '>= 0
|
20
|
+
s.add_development_dependency 'faraday', '>= 1.1.0'
|
21
21
|
s.add_development_dependency 'http'
|
22
22
|
s.add_development_dependency 'httpi'
|
23
23
|
s.add_development_dependency 'multipart-post', '~> 2.0'
|
24
24
|
s.add_development_dependency 'pry'
|
25
25
|
s.add_development_dependency 'rake'
|
26
26
|
s.add_development_dependency 'rest-client', '~> 2.0'
|
27
|
+
s.add_development_dependency 'grape', '~> 1.1.0'
|
27
28
|
s.add_development_dependency 'rspec', '~> 3.4'
|
29
|
+
s.add_development_dependency 'rexml'
|
28
30
|
|
29
31
|
s.files = `git ls-files`.split("\n")
|
30
32
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/gemfiles/http4.gemfile
CHANGED
data/lib/api_auth.rb
CHANGED
@@ -9,6 +9,7 @@ require 'api_auth/request_drivers/net_http'
|
|
9
9
|
require 'api_auth/request_drivers/curb'
|
10
10
|
require 'api_auth/request_drivers/rest_client'
|
11
11
|
require 'api_auth/request_drivers/action_controller'
|
12
|
+
require 'api_auth/request_drivers/grape_request'
|
12
13
|
require 'api_auth/request_drivers/action_dispatch'
|
13
14
|
require 'api_auth/request_drivers/rack'
|
14
15
|
require 'api_auth/request_drivers/httpi'
|
data/lib/api_auth/base.rb
CHANGED
@@ -22,7 +22,7 @@ module ApiAuth
|
|
22
22
|
def sign!(request, access_id, secret_key, options = {})
|
23
23
|
options = { override_http_method: nil, digest: 'sha1' }.merge(options)
|
24
24
|
headers = Headers.new(request)
|
25
|
-
headers.
|
25
|
+
headers.calculate_hash
|
26
26
|
headers.set_date
|
27
27
|
headers.sign_header auth_header(headers, access_id, secret_key, options)
|
28
28
|
end
|
@@ -39,7 +39,7 @@ module ApiAuth
|
|
39
39
|
# 900 seconds is 15 minutes
|
40
40
|
clock_skew = options.fetch(:clock_skew, 900)
|
41
41
|
|
42
|
-
if headers.
|
42
|
+
if headers.content_hash_mismatch?
|
43
43
|
false
|
44
44
|
elsif !signatures_match?(headers, secret_key, options)
|
45
45
|
false
|
@@ -71,7 +71,7 @@ module ApiAuth
|
|
71
71
|
|
72
72
|
private
|
73
73
|
|
74
|
-
AUTH_HEADER_PATTERN = /APIAuth(?:-HMAC-(MD5|SHA(?:1|224|256|384|512)?))? ([^:]+):(.+)
|
74
|
+
AUTH_HEADER_PATTERN = /APIAuth(?:-HMAC-(MD5|SHA(?:1|224|256|384|512)?))? ([^:]+):(.+)$/.freeze
|
75
75
|
|
76
76
|
def request_within_time_window?(headers, clock_skew)
|
77
77
|
Time.httpdate(headers.timestamp).utc > (Time.now.utc - clock_skew) &&
|
@@ -105,7 +105,7 @@ module ApiAuth
|
|
105
105
|
end
|
106
106
|
|
107
107
|
def hmac_signature(headers, secret_key, options)
|
108
|
-
canonical_string = headers.canonical_string(options[:override_http_method])
|
108
|
+
canonical_string = headers.canonical_string(options[:override_http_method], options[:headers_to_sign])
|
109
109
|
digest = OpenSSL::Digest.new(options[:digest])
|
110
110
|
b64_encode(OpenSSL::HMAC.digest(digest, secret_key, canonical_string))
|
111
111
|
end
|
data/lib/api_auth/headers.rb
CHANGED
@@ -26,6 +26,8 @@ module ApiAuth
|
|
26
26
|
else
|
27
27
|
ActionControllerRequest.new(request)
|
28
28
|
end
|
29
|
+
when /Grape::Request/
|
30
|
+
GrapeRequest.new(request)
|
29
31
|
when /ActionDispatch::Request/
|
30
32
|
ActionDispatchRequest.new(request)
|
31
33
|
when /ActionController::CgiRequest/
|
@@ -40,6 +42,7 @@ module ApiAuth
|
|
40
42
|
|
41
43
|
return new_request if new_request
|
42
44
|
return RackRequest.new(request) if request.is_a?(Rack::Request)
|
45
|
+
|
43
46
|
raise UnknownHTTPRequest, "#{request.class} is not yet supported."
|
44
47
|
end
|
45
48
|
private :initialize_request_driver
|
@@ -49,16 +52,24 @@ module ApiAuth
|
|
49
52
|
@request.timestamp
|
50
53
|
end
|
51
54
|
|
52
|
-
def canonical_string(override_method = nil)
|
55
|
+
def canonical_string(override_method = nil, headers_to_sign = [])
|
53
56
|
request_method = override_method || @request.http_method
|
54
57
|
|
55
58
|
raise ArgumentError, 'unable to determine the http method from the request, please supply an override' if request_method.nil?
|
56
59
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
headers = @request.fetch_headers
|
61
|
+
|
62
|
+
canonical_array = [request_method.upcase,
|
63
|
+
@request.content_type,
|
64
|
+
@request.content_hash,
|
65
|
+
parse_uri(@request.original_uri || @request.request_uri),
|
66
|
+
@request.timestamp]
|
67
|
+
|
68
|
+
if headers_to_sign.is_a?(Array) && headers_to_sign.any?
|
69
|
+
headers_to_sign.each { |h| canonical_array << headers[h] if headers[h].present? }
|
70
|
+
end
|
71
|
+
|
72
|
+
canonical_array.join(',')
|
62
73
|
end
|
63
74
|
|
64
75
|
# Returns the authorization header from the request's headers
|
@@ -70,15 +81,15 @@ module ApiAuth
|
|
70
81
|
@request.set_date if @request.timestamp.nil?
|
71
82
|
end
|
72
83
|
|
73
|
-
def
|
74
|
-
@request.
|
84
|
+
def calculate_hash
|
85
|
+
@request.populate_content_hash if @request.content_hash.nil?
|
75
86
|
end
|
76
87
|
|
77
|
-
def
|
78
|
-
if @request.
|
88
|
+
def content_hash_mismatch?
|
89
|
+
if @request.content_hash.nil?
|
79
90
|
false
|
80
91
|
else
|
81
|
-
@request.
|
92
|
+
@request.content_hash_mismatch?
|
82
93
|
end
|
83
94
|
end
|
84
95
|
|
data/lib/api_auth/helpers.rb
CHANGED
data/lib/api_auth/railtie.rb
CHANGED
@@ -13,7 +13,11 @@ module ApiAuth
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
if defined?(ActiveSupport)
|
17
|
+
ActiveSupport.on_load(:action_controller) do
|
18
|
+
ActionController::Base.include(ControllerMethods::InstanceMethods)
|
19
|
+
end
|
20
|
+
end
|
17
21
|
end # ControllerMethods
|
18
22
|
|
19
23
|
module ActiveResourceExtension # :nodoc:
|
@@ -69,7 +73,9 @@ module ApiAuth
|
|
69
73
|
tmp = "Net::HTTP::#{method.to_s.capitalize}".constantize.new(path, h)
|
70
74
|
tmp.body = arguments[0] if arguments.length > 1
|
71
75
|
ApiAuth.sign!(tmp, hmac_access_id, hmac_secret_key, api_auth_options)
|
72
|
-
|
76
|
+
if tmp['X-Authorization-Content-SHA256']
|
77
|
+
arguments.last['X-Authorization-Content-SHA256'] = tmp['X-Authorization-Content-SHA256']
|
78
|
+
end
|
73
79
|
arguments.last['DATE'] = tmp['DATE']
|
74
80
|
arguments.last['Authorization'] = tmp['Authorization']
|
75
81
|
end
|
@@ -78,9 +84,11 @@ module ApiAuth
|
|
78
84
|
end
|
79
85
|
end # Connection
|
80
86
|
|
81
|
-
if defined?(
|
82
|
-
|
83
|
-
|
87
|
+
if defined?(ActiveSupport)
|
88
|
+
ActiveSupport.on_load(:active_resource) do
|
89
|
+
ActiveResource::Base.include(ActiveResourceApiAuth)
|
90
|
+
ActiveResource::Connection.include(Connection)
|
91
|
+
end
|
84
92
|
end
|
85
93
|
end # ActiveResourceExtension
|
86
94
|
end # Rails
|
@@ -15,20 +15,21 @@ module ApiAuth
|
|
15
15
|
@request
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
18
|
+
def calculated_hash
|
19
19
|
body = @request.raw_post
|
20
|
-
|
20
|
+
sha256_base64digest(body)
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
23
|
+
def populate_content_hash
|
24
24
|
return unless @request.put? || @request.post?
|
25
|
-
|
25
|
+
|
26
|
+
@request.env['X-AUTHORIZATION-CONTENT-SHA256'] = calculated_hash
|
26
27
|
fetch_headers
|
27
28
|
end
|
28
29
|
|
29
|
-
def
|
30
|
+
def content_hash_mismatch?
|
30
31
|
if @request.put? || @request.post?
|
31
|
-
|
32
|
+
calculated_hash != content_hash
|
32
33
|
else
|
33
34
|
false
|
34
35
|
end
|
@@ -46,8 +47,8 @@ module ApiAuth
|
|
46
47
|
find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
|
47
48
|
end
|
48
49
|
|
49
|
-
def
|
50
|
-
find_header(%w[CONTENT-
|
50
|
+
def content_hash
|
51
|
+
find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
|
51
52
|
end
|
52
53
|
|
53
54
|
def original_uri
|
@@ -15,11 +15,11 @@ module ApiAuth
|
|
15
15
|
@request
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
18
|
+
def populate_content_hash
|
19
19
|
nil # doesn't appear to be possible
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
22
|
+
def content_hash_mismatch?
|
23
23
|
false
|
24
24
|
end
|
25
25
|
|
@@ -35,8 +35,8 @@ module ApiAuth
|
|
35
35
|
find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
39
|
-
find_header(%w[CONTENT-
|
38
|
+
def content_hash
|
39
|
+
find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
|
40
40
|
end
|
41
41
|
|
42
42
|
def original_uri
|
@@ -15,20 +15,21 @@ module ApiAuth
|
|
15
15
|
@request
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
body = @request.body
|
20
|
-
|
18
|
+
def calculated_hash
|
19
|
+
body = @request.body || ''
|
20
|
+
sha256_base64digest(body)
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
return unless %w[POST PUT].include?(@request.
|
25
|
-
|
23
|
+
def populate_content_hash
|
24
|
+
return unless %w[POST PUT].include?(@request.http_method.to_s.upcase)
|
25
|
+
|
26
|
+
@request.headers['X-Authorization-Content-SHA256'] = calculated_hash
|
26
27
|
fetch_headers
|
27
28
|
end
|
28
29
|
|
29
|
-
def
|
30
|
-
if %w[POST PUT].include?(@request.
|
31
|
-
|
30
|
+
def content_hash_mismatch?
|
31
|
+
if %w[POST PUT].include?(@request.http_method.to_s.upcase)
|
32
|
+
calculated_hash != content_hash
|
32
33
|
else
|
33
34
|
false
|
34
35
|
end
|
@@ -39,15 +40,15 @@ module ApiAuth
|
|
39
40
|
end
|
40
41
|
|
41
42
|
def http_method
|
42
|
-
@request.
|
43
|
+
@request.http_method.to_s.upcase
|
43
44
|
end
|
44
45
|
|
45
46
|
def content_type
|
46
47
|
find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
|
47
48
|
end
|
48
49
|
|
49
|
-
def
|
50
|
-
find_header(%w[
|
50
|
+
def content_hash
|
51
|
+
find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
|
51
52
|
end
|
52
53
|
|
53
54
|
def original_uri
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module ApiAuth
|
2
|
+
module RequestDrivers # :nodoc:
|
3
|
+
class GrapeRequest # :nodoc:
|
4
|
+
include ApiAuth::Helpers
|
5
|
+
|
6
|
+
def initialize(request)
|
7
|
+
@request = request
|
8
|
+
save_headers
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_auth_header(header)
|
13
|
+
@request.env['HTTP_AUTHORIZATION'] = header
|
14
|
+
save_headers # enforce update of processed_headers based on last updated headers
|
15
|
+
@request
|
16
|
+
end
|
17
|
+
|
18
|
+
def calculated_hash
|
19
|
+
body = @request.body.read
|
20
|
+
@request.body.rewind
|
21
|
+
sha256_base64digest(body)
|
22
|
+
end
|
23
|
+
|
24
|
+
def populate_content_hash
|
25
|
+
return if !@request.put? && !@request.post?
|
26
|
+
|
27
|
+
@request.env['HTTP_X_AUTHORIZATION_CONTENT_SHA256'] = calculated_hash
|
28
|
+
save_headers
|
29
|
+
end
|
30
|
+
|
31
|
+
def content_hash_mismatch?
|
32
|
+
if @request.put? || @request.post?
|
33
|
+
calculated_hash != content_hash
|
34
|
+
else
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch_headers
|
40
|
+
capitalize_keys @request.env
|
41
|
+
end
|
42
|
+
|
43
|
+
def http_method
|
44
|
+
@request.request_method.upcase
|
45
|
+
end
|
46
|
+
|
47
|
+
def content_type
|
48
|
+
find_header %w[HTTP_X_HMAC_CONTENT_TYPE HTTP_X_CONTENT_TYPE CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE]
|
49
|
+
end
|
50
|
+
|
51
|
+
def content_hash
|
52
|
+
find_header %w[HTTP_X_AUTHORIZATION_CONTENT_SHA256]
|
53
|
+
end
|
54
|
+
|
55
|
+
def original_uri
|
56
|
+
find_header %w[HTTP_X_HMAC_ORIGINAL_URI HTTP_X_ORIGINAL_URI X-ORIGINAL-URI X_ORIGINAL_URI]
|
57
|
+
end
|
58
|
+
|
59
|
+
def request_uri
|
60
|
+
@request.url
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_date
|
64
|
+
@request.env['HTTP_DATE'] = Time.now.utc.httpdate
|
65
|
+
save_headers
|
66
|
+
end
|
67
|
+
|
68
|
+
def timestamp
|
69
|
+
find_header %w[HTTP_X_HMAC_DATE HTTP_X_DATE DATE HTTP_DATE]
|
70
|
+
end
|
71
|
+
|
72
|
+
def authorization_header
|
73
|
+
find_header %w[HTTP_X_HMAC_AUTHORIZATION HTTP_X_AUTHORIZATION Authorization AUTHORIZATION HTTP_AUTHORIZATION]
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def find_header(keys)
|
79
|
+
keys.map { |key| @headers[key] }.compact.first
|
80
|
+
end
|
81
|
+
|
82
|
+
def save_headers
|
83
|
+
@headers = fetch_headers
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|