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
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.2.1
1
+ 2.5.0
data/api_auth.gemspec CHANGED
@@ -1,4 +1,4 @@
1
- $LOAD_PATH.push File.expand_path('../lib', __FILE__)
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.1.0'
12
+ s.required_ruby_version = '>= 2.5.0'
13
13
 
14
- s.add_development_dependency 'actionpack', '< 6.0', '> 4.0'
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.0', '> 4.0'
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.10'
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")
@@ -1,7 +1,7 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
- gem "http", github: "httprb/http"
5
+ gem 'http', '~> 4.0'
6
6
 
7
- gemspec :path => "../"
7
+ gemspec path: '../'
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'actionpack', '~> 5.2.1'
6
+ gem 'activeresource', '~> 5.1.0'
7
+ gem 'activesupport', '~> 5.2.1'
8
+
9
+ gemspec path: '../'
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'actionpack', '~> 6.0.0'
6
+ gem 'activeresource', '~> 5.1.0'
7
+ gem 'activesupport', '~> 6.0.0'
8
+
9
+ gemspec path: '../'
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'actionpack', '~> 6.1.0'
6
+ gem 'activeresource', '~> 5.1.0'
7
+ gem 'activesupport', '~> 6.1.0'
8
+
9
+ gem 'rubocop'
10
+
11
+ gemspec path: '../'
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.calculate_md5
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.md5_mismatch?
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
@@ -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
- [request_method.upcase,
58
- @request.content_type,
59
- @request.content_md5,
60
- parse_uri(@request.original_uri || @request.request_uri),
61
- @request.timestamp].join(',')
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 calculate_md5
74
- @request.populate_content_md5 if @request.content_md5.nil?
84
+ def calculate_hash
85
+ @request.populate_content_hash if @request.content_hash.nil?
75
86
  end
76
87
 
77
- def md5_mismatch?
78
- if @request.content_md5.nil?
88
+ def content_hash_mismatch?
89
+ if @request.content_hash.nil?
79
90
  false
80
91
  else
81
- @request.md5_mismatch?
92
+ @request.content_hash_mismatch?
82
93
  end
83
94
  end
84
95
 
@@ -4,8 +4,8 @@ module ApiAuth
4
4
  Base64.strict_encode64(string)
5
5
  end
6
6
 
7
- def md5_base64digest(string)
8
- Digest::MD5.base64digest(string)
7
+ def sha256_base64digest(string)
8
+ Digest::SHA256.base64digest(string)
9
9
  end
10
10
 
11
11
  # Capitalizes the keys of a hash
@@ -13,7 +13,11 @@ module ApiAuth
13
13
  end
14
14
  end
15
15
 
16
- ActionController::Base.send(:include, ControllerMethods::InstanceMethods) if defined?(ActionController::Base)
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
- arguments.last['Content-MD5'] = tmp['Content-MD5'] if tmp['Content-MD5']
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?(ActiveResource)
82
- ActiveResource::Base.send(:include, ActiveResourceApiAuth)
83
- ActiveResource::Connection.send(:include, Connection)
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 calculated_md5
18
+ def calculated_hash
19
19
  body = @request.raw_post
20
- md5_base64digest(body)
20
+ sha256_base64digest(body)
21
21
  end
22
22
 
23
- def populate_content_md5
23
+ def populate_content_hash
24
24
  return unless @request.put? || @request.post?
25
- @request.env['Content-MD5'] = calculated_md5
25
+
26
+ @request.env['X-AUTHORIZATION-CONTENT-SHA256'] = calculated_hash
26
27
  fetch_headers
27
28
  end
28
29
 
29
- def md5_mismatch?
30
+ def content_hash_mismatch?
30
31
  if @request.put? || @request.post?
31
- calculated_md5 != content_md5
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 content_md5
50
- find_header(%w[CONTENT-MD5 CONTENT_MD5 HTTP_CONTENT_MD5])
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 populate_content_md5
18
+ def populate_content_hash
19
19
  nil # doesn't appear to be possible
20
20
  end
21
21
 
22
- def md5_mismatch?
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 content_md5
39
- find_header(%w[CONTENT-MD5 CONTENT_MD5])
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 calculated_md5
19
- body = @request.body ? @request.body : ''
20
- md5_base64digest(body)
18
+ def calculated_hash
19
+ body = @request.body || ''
20
+ sha256_base64digest(body)
21
21
  end
22
22
 
23
- def populate_content_md5
24
- return unless %w[POST PUT].include?(@request.method.to_s.upcase)
25
- @request.headers['Content-MD5'] = calculated_md5
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 md5_mismatch?
30
- if %w[POST PUT].include?(@request.method.to_s.upcase)
31
- calculated_md5 != content_md5
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.method.to_s.upcase
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 content_md5
50
- find_header(%w[CONTENT-MD5 CONTENT_MD5 HTTP-CONTENT-MD5 HTTP_CONTENT_MD5])
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