api-auth 1.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.
Files changed (68) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/main.yml +71 -0
  3. data/.gitignore +13 -44
  4. data/.rubocop.yml +39 -0
  5. data/.rubocop_todo.yml +83 -0
  6. data/Appraisals +12 -36
  7. data/CHANGELOG.md +75 -1
  8. data/README.md +155 -52
  9. data/Rakefile +1 -1
  10. data/VERSION +1 -1
  11. data/api_auth.gemspec +35 -23
  12. data/gemfiles/rails_60.gemfile +9 -0
  13. data/gemfiles/rails_61.gemfile +9 -0
  14. data/gemfiles/rails_70.gemfile +9 -0
  15. data/lib/api-auth.rb +1 -1
  16. data/lib/api_auth/base.rb +41 -35
  17. data/lib/api_auth/errors.rb +4 -3
  18. data/lib/api_auth/headers.rb +38 -42
  19. data/lib/api_auth/helpers.rb +7 -16
  20. data/lib/api_auth/railtie.rb +34 -74
  21. data/lib/api_auth/request_drivers/action_controller.rb +27 -27
  22. data/lib/api_auth/request_drivers/action_dispatch.rb +0 -6
  23. data/lib/api_auth/request_drivers/curb.rb +16 -21
  24. data/lib/api_auth/request_drivers/faraday.rb +25 -34
  25. data/lib/api_auth/request_drivers/faraday_env.rb +102 -0
  26. data/lib/api_auth/request_drivers/grape_request.rb +87 -0
  27. data/lib/api_auth/request_drivers/http.rb +96 -0
  28. data/lib/api_auth/request_drivers/httpi.rb +22 -27
  29. data/lib/api_auth/request_drivers/net_http.rb +21 -26
  30. data/lib/api_auth/request_drivers/rack.rb +23 -28
  31. data/lib/api_auth/request_drivers/rest_client.rb +24 -29
  32. data/lib/api_auth.rb +4 -0
  33. data/lib/faraday/api_auth/middleware.rb +35 -0
  34. data/lib/faraday/api_auth.rb +8 -0
  35. data/spec/api_auth_spec.rb +135 -96
  36. data/spec/faraday_middleware_spec.rb +17 -0
  37. data/spec/headers_spec.rb +148 -108
  38. data/spec/helpers_spec.rb +8 -10
  39. data/spec/railtie_spec.rb +80 -99
  40. data/spec/request_drivers/action_controller_spec.rb +122 -79
  41. data/spec/request_drivers/action_dispatch_spec.rb +212 -85
  42. data/spec/request_drivers/curb_spec.rb +36 -33
  43. data/spec/request_drivers/faraday_env_spec.rb +188 -0
  44. data/spec/request_drivers/faraday_spec.rb +87 -83
  45. data/spec/request_drivers/grape_request_spec.rb +280 -0
  46. data/spec/request_drivers/http_spec.rb +190 -0
  47. data/spec/request_drivers/httpi_spec.rb +59 -59
  48. data/spec/request_drivers/net_http_spec.rb +70 -66
  49. data/spec/request_drivers/rack_spec.rb +101 -97
  50. data/spec/request_drivers/rest_client_spec.rb +218 -144
  51. data/spec/spec_helper.rb +15 -12
  52. metadata +144 -83
  53. data/.travis.yml +0 -40
  54. data/Gemfile.lock +0 -115
  55. data/gemfiles/rails_23.gemfile +0 -9
  56. data/gemfiles/rails_23.gemfile.lock +0 -70
  57. data/gemfiles/rails_30.gemfile +0 -9
  58. data/gemfiles/rails_30.gemfile.lock +0 -92
  59. data/gemfiles/rails_31.gemfile +0 -9
  60. data/gemfiles/rails_31.gemfile.lock +0 -98
  61. data/gemfiles/rails_32.gemfile +0 -9
  62. data/gemfiles/rails_32.gemfile.lock +0 -97
  63. data/gemfiles/rails_4.gemfile +0 -9
  64. data/gemfiles/rails_4.gemfile.lock +0 -94
  65. data/gemfiles/rails_41.gemfile +0 -9
  66. data/gemfiles/rails_41.gemfile.lock +0 -98
  67. data/gemfiles/rails_42.gemfile +0 -9
  68. data/gemfiles/rails_42.gemfile.lock +0 -115
@@ -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
@@ -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
@@ -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_hash
16
+ sha256_base64digest(body)
17
+ end
18
+
19
+ def populate_content_hash
20
+ return unless %w[POST PUT].include?(http_method)
21
+
22
+ @request['X-Authorization-Content-SHA256'] = calculated_hash
23
+ end
24
+
25
+ def content_hash_mismatch?
26
+ if %w[POST PUT].include?(http_method)
27
+ calculated_hash != content_hash
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_hash
42
+ find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
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
@@ -1,9 +1,6 @@
1
1
  module ApiAuth
2
-
3
2
  module RequestDrivers # :nodoc:
4
-
5
3
  class HttpiRequest # :nodoc:
6
-
7
4
  include ApiAuth::Helpers
8
5
 
9
6
  def initialize(request)
@@ -13,25 +10,25 @@ module ApiAuth
13
10
  end
14
11
 
15
12
  def set_auth_header(header)
16
- @request.headers["Authorization"] = header
13
+ @request.headers['Authorization'] = header
17
14
  fetch_headers
18
15
  @request
19
16
  end
20
17
 
21
- def calculated_md5
22
- md5_base64digest(@request.body || '')
18
+ def calculated_hash
19
+ sha256_base64digest(@request.body || '')
23
20
  end
24
21
 
25
- def populate_content_md5
26
- if @request.body
27
- @request.headers["Content-MD5"] = calculated_md5
28
- fetch_headers
29
- end
22
+ def populate_content_hash
23
+ return unless @request.body
24
+
25
+ @request.headers['X-Authorization-Content-SHA256'] = calculated_hash
26
+ fetch_headers
30
27
  end
31
28
 
32
- def md5_mismatch?
29
+ def content_hash_mismatch?
33
30
  if @request.body
34
- calculated_md5 != content_md5
31
+ calculated_hash != content_hash
35
32
  else
36
33
  false
37
34
  end
@@ -46,13 +43,15 @@ module ApiAuth
46
43
  end
47
44
 
48
45
  def content_type
49
- value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
50
- value.nil? ? "" : value
46
+ find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
47
+ end
48
+
49
+ def content_hash
50
+ find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
51
51
  end
52
52
 
53
- def content_md5
54
- value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
55
- value.nil? ? "" : value
53
+ def original_uri
54
+ find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
56
55
  end
57
56
 
58
57
  def request_uri
@@ -60,27 +59,23 @@ module ApiAuth
60
59
  end
61
60
 
62
61
  def set_date
63
- @request.headers["DATE"] = Time.now.utc.httpdate
62
+ @request.headers['DATE'] = Time.now.utc.httpdate
64
63
  fetch_headers
65
64
  end
66
65
 
67
66
  def timestamp
68
- value = find_header(%w(DATE HTTP_DATE))
69
- value.nil? ? "" : value
67
+ find_header(%w[DATE HTTP_DATE])
70
68
  end
71
69
 
72
70
  def authorization_header
73
- find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
71
+ find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION]
74
72
  end
75
73
 
76
- private
74
+ private
77
75
 
78
76
  def find_header(keys)
79
- keys.map {|key| @headers[key] }.compact.first
77
+ keys.map { |key| @headers[key] }.compact.first
80
78
  end
81
-
82
79
  end
83
-
84
80
  end
85
-
86
81
  end
@@ -1,9 +1,6 @@
1
1
  module ApiAuth
2
-
3
2
  module RequestDrivers # :nodoc:
4
-
5
3
  class NetHttpRequest # :nodoc:
6
-
7
4
  include ApiAuth::Helpers
8
5
 
9
6
  def initialize(request)
@@ -13,12 +10,12 @@ module ApiAuth
13
10
  end
14
11
 
15
12
  def set_auth_header(header)
16
- @request["Authorization"] = header
13
+ @request['Authorization'] = header
17
14
  @headers = fetch_headers
18
15
  @request
19
16
  end
20
17
 
21
- def calculated_md5
18
+ def calculated_hash
22
19
  if @request.respond_to?(:body_stream) && @request.body_stream
23
20
  body = @request.body_stream.read
24
21
  @request.body_stream.rewind
@@ -26,18 +23,18 @@ module ApiAuth
26
23
  body = @request.body
27
24
  end
28
25
 
29
- md5_base64digest(body || '')
26
+ sha256_base64digest(body || '')
30
27
  end
31
28
 
32
- def populate_content_md5
33
- if @request.class::REQUEST_HAS_BODY
34
- @request["Content-MD5"] = calculated_md5
35
- end
29
+ def populate_content_hash
30
+ return unless @request.class::REQUEST_HAS_BODY
31
+
32
+ @request['X-Authorization-Content-SHA256'] = calculated_hash
36
33
  end
37
34
 
38
- def md5_mismatch?
35
+ def content_hash_mismatch?
39
36
  if @request.class::REQUEST_HAS_BODY
40
- calculated_md5 != content_md5
37
+ calculated_hash != content_hash
41
38
  else
42
39
  false
43
40
  end
@@ -52,13 +49,15 @@ module ApiAuth
52
49
  end
53
50
 
54
51
  def content_type
55
- value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
56
- value.nil? ? "" : value
52
+ find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
57
53
  end
58
54
 
59
- def content_md5
60
- value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
61
- value.nil? ? "" : value
55
+ def content_hash
56
+ find_header(%w[X-Authorization-Content-SHA256])
57
+ end
58
+
59
+ def original_uri
60
+ find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
62
61
  end
63
62
 
64
63
  def request_uri
@@ -66,26 +65,22 @@ module ApiAuth
66
65
  end
67
66
 
68
67
  def set_date
69
- @request["DATE"] = Time.now.utc.httpdate
68
+ @request['DATE'] = Time.now.utc.httpdate
70
69
  end
71
70
 
72
71
  def timestamp
73
- value = find_header(%w(DATE HTTP_DATE))
74
- value.nil? ? "" : value
72
+ find_header(%w[DATE HTTP_DATE])
75
73
  end
76
74
 
77
75
  def authorization_header
78
- find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
76
+ find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION]
79
77
  end
80
78
 
81
- private
79
+ private
82
80
 
83
81
  def find_header(keys)
84
- keys.map {|key| @headers[key] }.compact.first
82
+ keys.map { |key| @headers[key] }.compact.first
85
83
  end
86
-
87
84
  end
88
-
89
85
  end
90
-
91
86
  end
@@ -1,9 +1,6 @@
1
1
  module ApiAuth
2
-
3
2
  module RequestDrivers # :nodoc:
4
-
5
3
  class RackRequest # :nodoc:
6
-
7
4
  include ApiAuth::Helpers
8
5
 
9
6
  def initialize(request)
@@ -13,31 +10,31 @@ module ApiAuth
13
10
  end
14
11
 
15
12
  def set_auth_header(header)
16
- @request.env.merge!({ "Authorization" => header })
13
+ @request.env['Authorization'] = header
17
14
  fetch_headers
18
15
  @request
19
16
  end
20
17
 
21
- def calculated_md5
18
+ def calculated_hash
22
19
  if @request.body
23
20
  body = @request.body.read
24
21
  @request.body.rewind
25
22
  else
26
23
  body = ''
27
24
  end
28
- md5_base64digest(body)
25
+ sha256_base64digest(body)
29
26
  end
30
27
 
31
- def populate_content_md5
32
- if ['POST', 'PUT'].include?(@request.request_method)
33
- @request.env["Content-MD5"] = calculated_md5
34
- fetch_headers
35
- end
28
+ def populate_content_hash
29
+ return unless %w[POST PUT].include?(@request.request_method)
30
+
31
+ @request.env['X-Authorization-Content-SHA256'] = calculated_hash
32
+ fetch_headers
36
33
  end
37
34
 
38
- def md5_mismatch?
39
- if ['POST', 'PUT'].include?(@request.request_method)
40
- calculated_md5 != content_md5
35
+ def content_hash_mismatch?
36
+ if %w[POST PUT].include?(@request.request_method)
37
+ calculated_hash != content_hash
41
38
  else
42
39
  false
43
40
  end
@@ -52,13 +49,15 @@ module ApiAuth
52
49
  end
53
50
 
54
51
  def content_type
55
- value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
56
- value.nil? ? "" : value
52
+ find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
57
53
  end
58
54
 
59
- def content_md5
60
- value = find_header(%w(CONTENT-MD5 CONTENT_MD5 HTTP-CONTENT-MD5 HTTP_CONTENT_MD5))
61
- value.nil? ? "" : value
55
+ def content_hash
56
+ find_header(%w[X-AUTHORIZATION-CONTENT-SHA256])
57
+ end
58
+
59
+ def original_uri
60
+ find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
62
61
  end
63
62
 
64
63
  def request_uri
@@ -66,27 +65,23 @@ module ApiAuth
66
65
  end
67
66
 
68
67
  def set_date
69
- @request.env.merge!({ "DATE" => Time.now.utc.httpdate })
68
+ @request.env['DATE'] = Time.now.utc.httpdate
70
69
  fetch_headers
71
70
  end
72
71
 
73
72
  def timestamp
74
- value = find_header(%w(DATE HTTP_DATE))
75
- value.nil? ? "" : value
73
+ find_header(%w[DATE HTTP_DATE])
76
74
  end
77
75
 
78
76
  def authorization_header
79
- find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
77
+ find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION]
80
78
  end
81
79
 
82
- private
80
+ private
83
81
 
84
82
  def find_header(keys)
85
- keys.map {|key| @headers[key] }.compact.first
83
+ keys.map { |key| @headers[key] }.compact.first
86
84
  end
87
-
88
85
  end
89
-
90
86
  end
91
-
92
87
  end