api-auth 2.2.0 → 2.4.1

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +11 -52
  3. data/.rubocop_todo.yml +92 -0
  4. data/.travis.yml +15 -14
  5. data/CHANGELOG.md +28 -0
  6. data/Gemfile +1 -1
  7. data/README.md +77 -38
  8. data/VERSION +1 -1
  9. data/api_auth.gemspec +15 -11
  10. data/gemfiles/http2.gemfile +7 -0
  11. data/gemfiles/http3.gemfile +7 -0
  12. data/gemfiles/http4.gemfile +7 -0
  13. data/gemfiles/rails_5.gemfile +5 -7
  14. data/gemfiles/rails_51.gemfile +5 -5
  15. data/gemfiles/rails_52.gemfile +9 -0
  16. data/gemfiles/rails_60.gemfile +11 -0
  17. data/lib/api_auth.rb +3 -0
  18. data/lib/api_auth/base.rb +2 -2
  19. data/lib/api_auth/headers.rb +19 -8
  20. data/lib/api_auth/railtie.rb +9 -5
  21. data/lib/api_auth/request_drivers/action_controller.rb +1 -0
  22. data/lib/api_auth/request_drivers/faraday.rb +2 -1
  23. data/lib/api_auth/request_drivers/grape_request.rb +87 -0
  24. data/lib/api_auth/request_drivers/http.rb +96 -0
  25. data/lib/api_auth/request_drivers/httpi.rb +1 -0
  26. data/lib/api_auth/request_drivers/net_http.rb +1 -1
  27. data/lib/api_auth/request_drivers/rack.rb +1 -0
  28. data/lib/api_auth/request_drivers/rest_client.rb +3 -2
  29. data/spec/api_auth_spec.rb +7 -0
  30. data/spec/headers_spec.rb +26 -8
  31. data/spec/request_drivers/action_controller_spec.rb +10 -4
  32. data/spec/request_drivers/action_dispatch_spec.rb +17 -11
  33. data/spec/request_drivers/curb_spec.rb +9 -3
  34. data/spec/request_drivers/faraday_spec.rb +6 -0
  35. data/spec/request_drivers/grape_request_spec.rb +279 -0
  36. data/spec/request_drivers/http_spec.rb +190 -0
  37. data/spec/request_drivers/httpi_spec.rb +6 -0
  38. data/spec/request_drivers/net_http_spec.rb +6 -0
  39. data/spec/request_drivers/rack_spec.rb +6 -0
  40. data/spec/request_drivers/rest_client_spec.rb +93 -15
  41. data/spec/spec_helper.rb +3 -4
  42. metadata +102 -66
  43. data/gemfiles/rails_4.gemfile +0 -11
  44. data/gemfiles/rails_41.gemfile +0 -11
  45. data/gemfiles/rails_42.gemfile +0 -11
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.2.0
1
+ 2.4.1
@@ -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,19 +9,23 @@ Gem::Specification.new do |s|
9
9
  s.authors = ['Mauricio Gomes']
10
10
  s.email = 'mauricio@edge14.com'
11
11
 
12
- s.add_development_dependency 'appraisal'
13
- s.add_development_dependency 'rake'
12
+ s.required_ruby_version = '>= 2.4.0'
13
+
14
+ s.add_development_dependency 'actionpack', '< 6.1', '> 4.0'
15
+ s.add_development_dependency 'activeresource', '>= 4.0'
16
+ s.add_development_dependency 'activesupport', '< 6.1', '> 4.0'
14
17
  s.add_development_dependency 'amatch'
15
- s.add_development_dependency 'rspec', '~> 3.4'
16
- s.add_development_dependency 'actionpack', '< 6.0', '> 4.0'
17
- s.add_development_dependency 'activesupport', '< 6.0', '> 4.0'
18
- s.add_development_dependency 'activeresource', '~> 4.0'
19
- s.add_development_dependency 'rest-client', '~> 1.6.0'
20
- s.add_development_dependency 'curb', '~> 0.8.1'
21
- s.add_development_dependency 'httpi'
18
+ s.add_development_dependency 'appraisal'
19
+ s.add_development_dependency 'curb', '~> 0.8'
22
20
  s.add_development_dependency 'faraday', '>= 0.10'
21
+ s.add_development_dependency 'http'
22
+ s.add_development_dependency 'httpi'
23
23
  s.add_development_dependency 'multipart-post', '~> 2.0'
24
- s.add_development_dependency 'httparty', '~> 0.13.0'
24
+ s.add_development_dependency 'pry'
25
+ s.add_development_dependency 'rake'
26
+ s.add_development_dependency 'rest-client', '~> 2.0'
27
+ s.add_development_dependency 'grape', '~> 1.1.0'
28
+ s.add_development_dependency 'rspec', '~> 3.4'
25
29
 
26
30
  s.files = `git ls-files`.split("\n")
27
31
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'http', '~> 2.0'
6
+
7
+ gemspec path: '../'
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'http', '~> 3.0'
6
+
7
+ gemspec path: '../'
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'http', '~> 4.0'
6
+
7
+ gemspec path: '../'
@@ -1,11 +1,9 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
- gem "actionpack", "~> 5.0.2"
6
- gem "activeresource", "~> 5.0.0", git: 'https://github.com/rails/activeresource.git'
7
- gem "activesupport", "~> 5.0.2"
5
+ gem 'actionpack', '~> 5.0.2'
6
+ gem 'activeresource', '~> 5.0.0'
7
+ gem 'activesupport', '~> 5.0.2'
8
8
 
9
- gem "rubocop"
10
-
11
- gemspec :path => "../"
9
+ gemspec path: '../'
@@ -1,9 +1,9 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
- gem "actionpack", "~> 5.1.1"
6
- gem "activeresource", "~> 5.0.0", git: 'https://github.com/rails/activeresource.git'
7
- gem "activesupport", "~> 5.1.1"
5
+ gem 'actionpack', '~> 5.1.1'
6
+ gem 'activeresource', '~> 5.1.0'
7
+ gem 'activesupport', '~> 5.1.1'
8
8
 
9
- gemspec :path => "../"
9
+ 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,11 @@
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
+ gem 'rubocop'
10
+
11
+ gemspec path: '../'
@@ -1,5 +1,6 @@
1
1
  require 'openssl'
2
2
  require 'base64'
3
+ require 'time'
3
4
 
4
5
  require 'api_auth/errors'
5
6
  require 'api_auth/helpers'
@@ -8,10 +9,12 @@ require 'api_auth/request_drivers/net_http'
8
9
  require 'api_auth/request_drivers/curb'
9
10
  require 'api_auth/request_drivers/rest_client'
10
11
  require 'api_auth/request_drivers/action_controller'
12
+ require 'api_auth/request_drivers/grape_request'
11
13
  require 'api_auth/request_drivers/action_dispatch'
12
14
  require 'api_auth/request_drivers/rack'
13
15
  require 'api_auth/request_drivers/httpi'
14
16
  require 'api_auth/request_drivers/faraday'
17
+ require 'api_auth/request_drivers/http'
15
18
 
16
19
  require 'api_auth/headers'
17
20
  require 'api_auth/base'
@@ -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/
@@ -34,10 +36,13 @@ module ApiAuth
34
36
  HttpiRequest.new(request)
35
37
  when /Faraday::Request/
36
38
  FaradayRequest.new(request)
39
+ when /HTTP::Request/
40
+ HttpRequest.new(request)
37
41
  end
38
42
 
39
43
  return new_request if new_request
40
44
  return RackRequest.new(request) if request.is_a?(Rack::Request)
45
+
41
46
  raise UnknownHTTPRequest, "#{request.class} is not yet supported."
42
47
  end
43
48
  private :initialize_request_driver
@@ -47,18 +52,24 @@ module ApiAuth
47
52
  @request.timestamp
48
53
  end
49
54
 
50
- def canonical_string(override_method = nil)
55
+ def canonical_string(override_method = nil, headers_to_sign = [])
51
56
  request_method = override_method || @request.http_method
52
57
 
53
- if request_method.nil?
54
- raise ArgumentError, 'unable to determine the http method from the request, please supply an override'
58
+ raise ArgumentError, 'unable to determine the http method from the request, please supply an override' if request_method.nil?
59
+
60
+ headers = @request.fetch_headers
61
+
62
+ canonical_array = [request_method.upcase,
63
+ @request.content_type,
64
+ @request.content_md5,
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? }
55
70
  end
56
71
 
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(',')
72
+ canonical_array.join(',')
62
73
  end
63
74
 
64
75
  # Returns the authorization header from the request's headers
@@ -13,8 +13,10 @@ module ApiAuth
13
13
  end
14
14
  end
15
15
 
16
- if defined?(ActionController::Base)
17
- ActionController::Base.send(:include, ControllerMethods::InstanceMethods)
16
+ if defined?(ActiveSupport)
17
+ ActiveSupport.on_load(:action_controller) do
18
+ ActionController::Base.include(ControllerMethods::InstanceMethods)
19
+ end
18
20
  end
19
21
  end # ControllerMethods
20
22
 
@@ -80,9 +82,11 @@ module ApiAuth
80
82
  end
81
83
  end # Connection
82
84
 
83
- if defined?(ActiveResource)
84
- ActiveResource::Base.send(:include, ActiveResourceApiAuth)
85
- ActiveResource::Connection.send(:include, Connection)
85
+ if defined?(ActiveSupport)
86
+ ActiveSupport.on_load(:active_resource) do
87
+ ActiveResource::Base.include(ActiveResourceApiAuth)
88
+ ActiveResource::Connection.include(Connection)
89
+ end
86
90
  end
87
91
  end # ActiveResourceExtension
88
92
  end # Rails
@@ -22,6 +22,7 @@ module ApiAuth
22
22
 
23
23
  def populate_content_md5
24
24
  return unless @request.put? || @request.post?
25
+
25
26
  @request.env['Content-MD5'] = calculated_md5
26
27
  fetch_headers
27
28
  end
@@ -16,12 +16,13 @@ module ApiAuth
16
16
  end
17
17
 
18
18
  def calculated_md5
19
- body = @request.body ? @request.body : ''
19
+ body = @request.body || ''
20
20
  md5_base64digest(body)
21
21
  end
22
22
 
23
23
  def populate_content_md5
24
24
  return unless %w[POST PUT].include?(@request.method.to_s.upcase)
25
+
25
26
  @request.headers['Content-MD5'] = calculated_md5
26
27
  fetch_headers
27
28
  end
@@ -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_md5
19
+ body = @request.body.read
20
+ @request.body.rewind
21
+ md5_base64digest(body)
22
+ end
23
+
24
+ def populate_content_md5
25
+ return if !@request.put? && !@request.post?
26
+
27
+ @request.env['HTTP_CONTENT_MD5'] = calculated_md5
28
+ save_headers
29
+ end
30
+
31
+ def md5_mismatch?
32
+ if @request.put? || @request.post?
33
+ calculated_md5 != content_md5
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_md5
52
+ find_header %w[HTTP_X_HMAC_CONTENT_MD5 HTTP_X_CONTENT_MD5 CONTENT-MD5 CONTENT_MD5 HTTP_CONTENT_MD5]
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
@@ -0,0 +1,96 @@
1
+ module ApiAuth
2
+ module RequestDrivers # :nodoc:
3
+ class HttpRequest # :nodoc:
4
+ include ApiAuth::Helpers
5
+
6
+ def initialize(request)
7
+ @request = request
8
+ end
9
+
10
+ def set_auth_header(header)
11
+ @request['Authorization'] = header
12
+ @request
13
+ end
14
+
15
+ def calculated_md5
16
+ md5_base64digest(body)
17
+ end
18
+
19
+ def populate_content_md5
20
+ return unless %w[POST PUT].include?(http_method)
21
+
22
+ @request['Content-MD5'] = calculated_md5
23
+ end
24
+
25
+ def md5_mismatch?
26
+ if %w[POST PUT].include?(http_method)
27
+ calculated_md5 != content_md5
28
+ else
29
+ false
30
+ end
31
+ end
32
+
33
+ def http_method
34
+ @request.verb.to_s.upcase
35
+ end
36
+
37
+ def content_type
38
+ find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
39
+ end
40
+
41
+ def content_md5
42
+ find_header(%w[CONTENT-MD5 CONTENT_MD5])
43
+ end
44
+
45
+ def original_uri
46
+ find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
47
+ end
48
+
49
+ def request_uri
50
+ @request.uri.request_uri
51
+ end
52
+
53
+ def set_date
54
+ @request['Date'] = Time.now.utc.httpdate
55
+ end
56
+
57
+ def timestamp
58
+ find_header(%w[DATE HTTP_DATE])
59
+ end
60
+
61
+ def authorization_header
62
+ find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION]
63
+ end
64
+
65
+ def body
66
+ if body_source.respond_to?(:read)
67
+ result = body_source.read
68
+ body_source.rewind
69
+ result
70
+ else
71
+ body_source.to_s
72
+ end
73
+ end
74
+
75
+ def fetch_headers
76
+ capitalize_keys @request.headers.to_h
77
+ end
78
+
79
+ private
80
+
81
+ def find_header(keys)
82
+ keys.map { |key| @request[key] }.compact.first
83
+ end
84
+
85
+ def body_source
86
+ body = @request.body
87
+
88
+ if defined?(::HTTP::Request::Body)
89
+ body.respond_to?(:source) ? body.source : body.instance_variable_get(:@body)
90
+ else
91
+ body
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end