api-auth 2.2.1 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74847c2470f870ce0206badfda86189773a6bb96b109d0fca6ab276f5bb70523
4
- data.tar.gz: 5b24a84ec9a7617b3dfe33e9c97cccf5edc7caa627b51b76b3044f2952e32e80
3
+ metadata.gz: 92c94ba421b1f15829f9b16eb5bcbeb55c773298023134786011282639cba630
4
+ data.tar.gz: e14ce2ddf081464504b55a8aecd44afbc9526945a89c27e5a96d8bf500c1fa52
5
5
  SHA512:
6
- metadata.gz: 507f3c7f5e1570c9014e953179a42c13c5502ea50483172fd7650a6b9853297e9220cb096599c4498a95f1873bf06d37852355cb2bc566481c2b93c61a4a9cb6
7
- data.tar.gz: 05515f1bd95793f5c0497e2d3e0ea9cd65d9d14015f8f2abe151aa40ea0dfb849250505f0a132ba6b161d31d0839ebaec6adb7e8cef91dc286d123f7b04a1f37
6
+ metadata.gz: d392d56ebd5dd7592a363d623c51fe00750d97fc212114d37d6c79a650bc3c0c7bfa020bf6e4c2f207f6c2dd71ac67cabf6affbbe28d4c7b6c1d757c67abc90a
7
+ data.tar.gz: f88c2d4d14dc61b5f9e867aecf4c3680e117ef6194617447963ddea1b6f93e5d3de7e414bc2da12e4c251d5803a4a2d28e91f5d817f29809aa7cc260d299af16
@@ -1,5 +1,8 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
+ AllCops:
4
+ TargetRubyVersion: 2.3
5
+
3
6
  Metrics/AbcSize:
4
7
  Max: 25
5
8
 
@@ -14,3 +17,6 @@ Metrics/MethodLength:
14
17
  Naming/FileName:
15
18
  Exclude:
16
19
  - 'lib/api-auth.rb'
20
+
21
+ Style/FrozenStringLiteralComment:
22
+ Enabled: false
@@ -1,50 +1,82 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2018-02-12 13:27:16 +0300 using RuboCop version 0.52.1.
3
+ # on 2018-10-22 20:30:52 +0700 using RuboCop version 0.59.2.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ # Offense count: 1
10
+ # Cop supports --auto-correct.
11
+ # Configuration parameters: Include, TreatCommentsAsGroupSeparators.
12
+ # Include: **/*.gemspec
13
+ Gemspec/OrderedDependencies:
14
+ Exclude:
15
+ - 'api_auth.gemspec'
16
+
9
17
  # Offense count: 1
10
18
  # Configuration parameters: AllowSafeAssignment.
11
19
  Lint/AssignmentInCondition:
12
20
  Exclude:
13
21
  - 'lib/api_auth/base.rb'
14
22
 
15
- # Offense count: 8
23
+ # Offense count: 9
24
+ # Configuration parameters: CheckForMethodsWithNoSideEffects.
16
25
  Lint/Void:
17
26
  Exclude:
18
27
  - 'lib/api_auth/headers.rb'
19
28
  - 'lib/api_auth/request_drivers/action_controller.rb'
20
29
  - 'lib/api_auth/request_drivers/curb.rb'
21
30
  - 'lib/api_auth/request_drivers/faraday.rb'
31
+ - 'lib/api_auth/request_drivers/grape_request.rb'
22
32
  - 'lib/api_auth/request_drivers/httpi.rb'
23
33
  - 'lib/api_auth/request_drivers/net_http.rb'
24
34
  - 'lib/api_auth/request_drivers/rack.rb'
25
35
  - 'lib/api_auth/request_drivers/rest_client.rb'
26
36
 
37
+ # Offense count: 1
38
+ # Configuration parameters: CountComments, ExcludedMethods.
39
+ # ExcludedMethods: refine
40
+ Metrics/BlockLength:
41
+ Max: 27
42
+
27
43
  # Offense count: 1
28
44
  Metrics/CyclomaticComplexity:
29
- Max: 14
45
+ Max: 15
30
46
 
31
47
  # Offense count: 1
32
48
  Metrics/PerceivedComplexity:
33
49
  Max: 8
34
50
 
35
- # Offense count: 9
51
+ # Offense count: 10
36
52
  Naming/AccessorMethodName:
37
53
  Exclude:
38
54
  - 'lib/api_auth/railtie.rb'
39
55
  - 'lib/api_auth/request_drivers/action_controller.rb'
40
56
  - 'lib/api_auth/request_drivers/curb.rb'
41
57
  - 'lib/api_auth/request_drivers/faraday.rb'
58
+ - 'lib/api_auth/request_drivers/grape_request.rb'
42
59
  - 'lib/api_auth/request_drivers/http.rb'
43
60
  - 'lib/api_auth/request_drivers/httpi.rb'
44
61
  - 'lib/api_auth/request_drivers/net_http.rb'
45
62
  - 'lib/api_auth/request_drivers/rack.rb'
46
63
  - 'lib/api_auth/request_drivers/rest_client.rb'
47
64
 
65
+ # Offense count: 3
66
+ # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
67
+ # AllowedNames: io, id, to, by, on, in, at, ip, db
68
+ Naming/UncommunicativeMethodParamName:
69
+ Exclude:
70
+ - 'lib/api_auth/base.rb'
71
+ - 'spec/railtie_spec.rb'
72
+
73
+ # Offense count: 1
74
+ # Configuration parameters: EnforcedStyle.
75
+ # SupportedStyles: inline, group
76
+ Style/AccessModifierDeclarations:
77
+ Exclude:
78
+ - 'lib/api_auth/headers.rb'
79
+
48
80
  # Offense count: 9
49
81
  Style/CommentedKeyword:
50
82
  Exclude:
@@ -2,17 +2,16 @@ language: ruby
2
2
  sudo: false
3
3
  cache: bundler
4
4
  rvm:
5
- - 2.1.10
6
- - 2.2.9
7
5
  - 2.3.6
8
6
  - 2.4.3
9
- - 2.5.0
7
+ - 2.5.3
10
8
  gemfile:
11
9
  - gemfiles/rails_4.gemfile
12
10
  - gemfiles/rails_41.gemfile
13
11
  - gemfiles/rails_42.gemfile
14
12
  - gemfiles/rails_5.gemfile
15
13
  - gemfiles/rails_51.gemfile
14
+ - gemfiles/rails_52.gemfile
16
15
  - gemfiles/http2.gemfile
17
16
  - gemfiles/http3.gemfile
18
17
  - gemfiles/http4.gemfile
@@ -26,19 +25,8 @@ script:
26
25
  - bundle exec $TEST_SUITE
27
26
 
28
27
  matrix:
29
- exclude:
30
- - rvm: 2.1.10
31
- gemfile: gemfiles/rails_5.gemfile
32
- - rvm: 2.1.10
33
- gemfile: gemfiles/rails_51.gemfile
34
- - rvm: 2.1.9
35
- gemfile: gemfiles/http2.gemfile
36
- - rvm: 2.1.9
37
- gemfile: gemfiles/http3.gemfile
38
- - rvm: 2.1.9
39
- gemfile: gemfiles/http4.gemfile
40
28
  include:
41
- - rvm: 2.5.0
29
+ - rvm: 2.5.3
42
30
  gemfile: gemfiles/rails_5.gemfile
43
31
  env: TEST_SUITE="rubocop lib/ spec/"
44
32
 
@@ -1,3 +1,20 @@
1
+ # 2.3.0 (2018-10-23)
2
+ - Added support for Grape API (#169 phuongnd08 & dunghuynh)
3
+ - Added option for specifying customer headers to sign via new `headers_to_sign`
4
+ argument (#170 fakenine)
5
+ - Fix tests and drop support for Ruby < 2.3 (#171 fwininger)
6
+
7
+ # 2.2.0 (2018-03-12)
8
+ - Drop support ruby 1.x, rails 2.x, rails 3.x (#141 fwininger)
9
+ - Add http.rb request driver (#164 tycooon)
10
+ - Fix POST and PUT requests in RestClient (#151 fwininger)
11
+ - Allow clock skew to be user-defined (#136 mlarraz)
12
+ - Adds #original_uri method to all request drivers (#137 iMacTia)
13
+ - Rubocop and test fixes (fwininger & nicolasleger)
14
+ - Changed return type for request #content_md5 #timestamp #content_type (fwininger)
15
+ - Fix URI edge case where a URI contains another URI (zfletch)
16
+ - Updates to the README (zfletch)
17
+
1
18
  # 2.1.0 (2016-12-22)
2
19
  - Fixed a NoMethodError that might occur when using the NetHttp Driver (#130 grahamkenville)
3
20
  - More securely compare signatures in a way that prevents timing attacks (#56 leishman, #133 will0)
data/README.md CHANGED
@@ -51,9 +51,9 @@ minutes in order to avoid replay attacks.
51
51
 
52
52
  ## Requirement
53
53
 
54
- v3.X require Ruby >= 2.1 and Rails >= 4.0 if you use rails.
54
+ This gem require Ruby >= 2.3 and Rails >= 4.0 if you use rails.
55
55
 
56
- For older version of Ruby or Rails, please use ApiAuth v2.X.
56
+ For older version of Ruby or Rails, please use ApiAuth v2.1 and older.
57
57
 
58
58
  **IMPORTANT: v2.0.0 is backwards incompatible with the default settings of v1.x to address a security vulnerability. See [CHANGELOG.md](/CHANGELOG.md) for security update information.**
59
59
 
@@ -138,6 +138,14 @@ to:
138
138
 
139
139
  Authorization = APIAuth-HMAC-DIGEST_NAME 'client access id':'signature'
140
140
 
141
+ If you want to sign custom headers, you can pass them as an array of strings in the options like so:
142
+
143
+ ``` ruby
144
+ @signed_request = ApiAuth.sign!(@request, @access_id, @secret_key, headers_to_sign: %w[HTTP_HEADER_NAME])
145
+ ```
146
+
147
+ With the specified headers values being at the end of the canonical string in the same order.
148
+
141
149
  ### ActiveResource Clients
142
150
 
143
151
  ApiAuth can transparently protect your ActiveResource communications with a
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.2.1
1
+ 2.3.0
@@ -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,7 +9,7 @@ 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.3.0'
13
13
 
14
14
  s.add_development_dependency 'actionpack', '< 6.0', '> 4.0'
15
15
  s.add_development_dependency 'activeresource', '>= 4.0'
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
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'
28
29
 
29
30
  s.files = `git ls-files`.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", "~> 2.0"
5
+ gem 'http', '~> 2.0'
6
6
 
7
- gemspec :path => "../"
7
+ gemspec path: '../'
@@ -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", "~> 3.0"
5
+ gem 'http', '~> 3.0'
6
6
 
7
- gemspec :path => "../"
7
+ gemspec path: '../'
@@ -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', github: 'httprb/http'
6
6
 
7
- gemspec :path => "../"
7
+ gemspec path: '../'
@@ -1,11 +1,11 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
- gem "rake", "< 11.0", :platforms => :ruby_18
6
- gem "tins", "< 1.7", :platforms => :ruby_19
7
- gem "actionpack", "~> 4.0.4"
8
- gem "activeresource", "~> 4.0.0"
9
- gem "activesupport", "~> 4.0.4"
5
+ gem 'actionpack', '~> 4.0.4'
6
+ gem 'activeresource', '~> 4.0.0'
7
+ gem 'activesupport', '~> 4.0.4'
8
+ gem 'rake', '< 11.0', platforms: :ruby_18
9
+ gem 'tins', '< 1.7', platforms: :ruby_19
10
10
 
11
- gemspec :path => "../"
11
+ gemspec path: '../'
@@ -1,11 +1,11 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
- gem "rake", "< 11.0", :platforms => :ruby_18
6
- gem "tins", "< 1.7", :platforms => :ruby_19
7
- gem "actionpack", "~> 4.1.0"
8
- gem "activeresource", "~> 4.0.0"
9
- gem "activesupport", "~> 4.1.0"
5
+ gem 'actionpack', '~> 4.1.0'
6
+ gem 'activeresource', '~> 4.0.0'
7
+ gem 'activesupport', '~> 4.1.0'
8
+ gem 'rake', '< 11.0', platforms: :ruby_18
9
+ gem 'tins', '< 1.7', platforms: :ruby_19
10
10
 
11
- gemspec :path => "../"
11
+ gemspec path: '../'
@@ -1,11 +1,11 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
- gem "rake", "< 11.0", :platforms => :ruby_18
6
- gem "tins", "< 1.7", :platforms => :ruby_19
7
- gem "actionpack", "~> 4.2.0"
8
- gem "activeresource", "~> 4.0.0"
9
- gem "activesupport", "~> 4.2.0"
5
+ gem 'actionpack', '~> 4.2.0'
6
+ gem 'activeresource', '~> 4.0.0'
7
+ gem 'activesupport', '~> 4.2.0'
8
+ gem 'rake', '< 11.0', platforms: :ruby_18
9
+ gem 'tins', '< 1.7', platforms: :ruby_19
10
10
 
11
- gemspec :path => "../"
11
+ gemspec path: '../'
@@ -1,11 +1,11 @@
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', git: 'https://github.com/rails/activeresource.git'
7
+ gem 'activesupport', '~> 5.0.2'
8
8
 
9
- gem "rubocop"
9
+ gem 'rubocop'
10
10
 
11
- gemspec :path => "../"
11
+ 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.0.0', git: 'https://github.com/rails/activeresource.git'
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.0.0', git: 'https://github.com/rails/activeresource.git'
7
+ gem 'activesupport', '~> 5.2.1'
8
+
9
+ gemspec path: '../'
@@ -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'
@@ -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_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? }
70
+ end
71
+
72
+ canonical_array.join(',')
62
73
  end
63
74
 
64
75
  # Returns the authorization header from the request's headers
@@ -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
@@ -18,6 +18,7 @@ module ApiAuth
18
18
 
19
19
  def populate_content_md5
20
20
  return unless %w[POST PUT].include?(http_method)
21
+
21
22
  @request['Content-MD5'] = calculated_md5
22
23
  end
23
24
 
@@ -21,6 +21,7 @@ module ApiAuth
21
21
 
22
22
  def populate_content_md5
23
23
  return unless @request.body
24
+
24
25
  @request.headers['Content-MD5'] = calculated_md5
25
26
  fetch_headers
26
27
  end
@@ -28,6 +28,7 @@ module ApiAuth
28
28
 
29
29
  def populate_content_md5
30
30
  return unless @request.class::REQUEST_HAS_BODY
31
+
31
32
  @request['Content-MD5'] = calculated_md5
32
33
  end
33
34
 
@@ -27,6 +27,7 @@ module ApiAuth
27
27
 
28
28
  def populate_content_md5
29
29
  return unless %w[POST PUT].include?(@request.request_method)
30
+
30
31
  @request.env['Content-MD5'] = calculated_md5
31
32
  fetch_headers
32
33
  end
@@ -30,6 +30,7 @@ module ApiAuth
30
30
 
31
31
  def populate_content_md5
32
32
  return unless %w[post put].include?(@request.method.to_s)
33
+
33
34
  @request.headers['Content-MD5'] = calculated_md5
34
35
  save_headers
35
36
  end
@@ -169,6 +169,13 @@ describe 'ApiAuth' do
169
169
  expect(ApiAuth.authentic?(signed_request, '123', clock_skew: 60.seconds)).to eq false
170
170
  end
171
171
  end
172
+
173
+ context 'when passed the headers_to_sign option' do
174
+ it 'validates the request' do
175
+ request['X-Forwarded-For'] = '192.168.1.1'
176
+ expect(ApiAuth.authentic?(signed_request, '123', headers_to_sign: %w[HTTP_X_FORWARDED_FOR])).to eq true
177
+ end
178
+ end
172
179
  end
173
180
 
174
181
  describe '.access_id' do
@@ -125,6 +125,32 @@ describe ApiAuth::Headers do
125
125
  end
126
126
  end
127
127
  end
128
+
129
+ context 'when headers to sign are provided' do
130
+ let(:request) do
131
+ Faraday::Request.create('GET') do |req|
132
+ req.options = Faraday::RequestOptions.new(Faraday::FlatParamsEncoder)
133
+ req.params = Faraday::Utils::ParamsHash.new
134
+ req.url('/resource.xml?foo=bar&bar=foo')
135
+ req.headers = { 'X-Forwarded-For' => '192.168.1.1' }
136
+ end
137
+ end
138
+ subject(:headers) { described_class.new(request) }
139
+ let(:driver) { headers.instance_variable_get('@request') }
140
+
141
+ before do
142
+ allow(driver).to receive(:content_type).and_return 'text/html'
143
+ allow(driver).to receive(:content_md5).and_return '12345'
144
+ allow(driver).to receive(:timestamp).and_return 'Mon, 23 Jan 1984 03:29:56 GMT'
145
+ end
146
+
147
+ context 'the driver uses the original_uri' do
148
+ it 'constructs the canonical_string with the original_uri' do
149
+ expect(headers.canonical_string(nil, %w[X-FORWARDED-FOR]))
150
+ .to eq 'GET,text/html,12345,/resource.xml?bar=foo&foo=bar,Mon, 23 Jan 1984 03:29:56 GMT,192.168.1.1'
151
+ end
152
+ end
153
+ end
128
154
  end
129
155
  end
130
156
 
@@ -0,0 +1,271 @@
1
+ require 'spec_helper'
2
+
3
+ describe ApiAuth::RequestDrivers::GrapeRequest do
4
+ let(:default_method) { 'PUT' }
5
+ let(:default_params) do
6
+ { 'message' => "hello\nworld" }
7
+ end
8
+ let(:default_options) do
9
+ {
10
+ method: method,
11
+ params: params
12
+ }
13
+ end
14
+ let(:default_env) do
15
+ Rack::MockRequest.env_for('/', options)
16
+ end
17
+ let(:method) { default_method }
18
+ let(:params) { default_params }
19
+ let(:options) { default_options.merge(request_headers) }
20
+ let(:env) { default_env }
21
+
22
+ let(:request) do
23
+ Grape::Request.new(env)
24
+ end
25
+
26
+ let(:timestamp) { Time.now.utc.httpdate }
27
+ let(:request_headers) do
28
+ {
29
+ 'HTTP_X_HMAC_AUTHORIZATION' => 'APIAuth 1044:12345',
30
+ 'HTTP_X_HMAC_CONTENT_MD5' => 'WEqCyXEuRBYZbohpZmUyAw==',
31
+ 'HTTP_X_HMAC_CONTENT_TYPE' => 'text/plain',
32
+ 'HTTP_X_HMAC_DATE' => timestamp
33
+ }
34
+ end
35
+
36
+ subject(:driven_request) { ApiAuth::RequestDrivers::GrapeRequest.new(request) }
37
+
38
+ describe 'getting headers correctly' do
39
+ it 'gets the content_type' do
40
+ expect(driven_request.content_type).to eq('text/plain')
41
+ end
42
+
43
+ it 'gets the content_md5' do
44
+ expect(driven_request.content_md5).to eq('WEqCyXEuRBYZbohpZmUyAw==')
45
+ end
46
+
47
+ it 'gets the request_uri' do
48
+ expect(driven_request.request_uri).to eq('http://example.org/')
49
+ end
50
+
51
+ it 'gets the timestamp' do
52
+ expect(driven_request.timestamp).to eq(timestamp)
53
+ end
54
+
55
+ it 'gets the authorization_header' do
56
+ expect(driven_request.authorization_header).to eq('APIAuth 1044:12345')
57
+ end
58
+
59
+ describe '#calculated_md5' do
60
+ it 'calculates md5 from the body' do
61
+ expect(driven_request.calculated_md5).to eq('WEqCyXEuRBYZbohpZmUyAw==')
62
+ end
63
+
64
+ context 'no body' do
65
+ let(:params) { {} }
66
+
67
+ it 'treats no body as empty string' do
68
+ expect(driven_request.calculated_md5).to eq('1B2M2Y8AsgTpgAmY7PhCfg==')
69
+ end
70
+ end
71
+ end
72
+
73
+ describe 'http_method' do
74
+ context 'when put request' do
75
+ let(:method) { 'put' }
76
+
77
+ it 'returns upcased put' do
78
+ expect(driven_request.http_method).to eq('PUT')
79
+ end
80
+ end
81
+
82
+ context 'when get request' do
83
+ let(:method) { 'get' }
84
+
85
+ it 'returns upcased get' do
86
+ expect(driven_request.http_method).to eq('GET')
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ describe 'setting headers correctly' do
93
+ let(:request_headers) do
94
+ {
95
+ 'HTTP_X_HMAC_CONTENT_TYPE' => 'text/plain'
96
+ }
97
+ end
98
+
99
+ describe '#populate_content_md5' do
100
+ context 'when getting' do
101
+ let(:method) { 'get' }
102
+
103
+ it "doesn't populate content-md5" do
104
+ driven_request.populate_content_md5
105
+ expect(request.headers['Content-Md5']).to be_nil
106
+ end
107
+ end
108
+
109
+ context 'when posting' do
110
+ let(:method) { 'post' }
111
+
112
+ it 'populates content-md5' do
113
+ driven_request.populate_content_md5
114
+ expect(request.headers['Content-Md5']).to eq('WEqCyXEuRBYZbohpZmUyAw==')
115
+ end
116
+
117
+ it 'refreshes the cached headers' do
118
+ driven_request.populate_content_md5
119
+ expect(driven_request.content_md5).to eq('WEqCyXEuRBYZbohpZmUyAw==')
120
+ end
121
+ end
122
+
123
+ context 'when putting' do
124
+ let(:method) { 'put' }
125
+
126
+ it 'populates content-md5' do
127
+ driven_request.populate_content_md5
128
+ expect(request.headers['Content-Md5']).to eq('WEqCyXEuRBYZbohpZmUyAw==')
129
+ end
130
+
131
+ it 'refreshes the cached headers' do
132
+ driven_request.populate_content_md5
133
+ expect(driven_request.content_md5).to eq('WEqCyXEuRBYZbohpZmUyAw==')
134
+ end
135
+ end
136
+
137
+ context 'when deleting' do
138
+ let(:method) { 'delete' }
139
+
140
+ it "doesn't populate content-md5" do
141
+ driven_request.populate_content_md5
142
+ expect(request.headers['Content-Md5']).to be_nil
143
+ end
144
+ end
145
+ end
146
+
147
+ describe '#set_date' do
148
+ before do
149
+ allow(Time).to receive_message_chain(:now, :utc, :httpdate).and_return(timestamp)
150
+ end
151
+
152
+ it 'sets the date header of the request' do
153
+ allow(Time).to receive_message_chain(:now, :utc, :httpdate).and_return(timestamp)
154
+ driven_request.set_date
155
+ expect(request.headers['Date']).to eq(timestamp)
156
+ end
157
+
158
+ it 'refreshes the cached headers' do
159
+ driven_request.set_date
160
+ expect(driven_request.timestamp).to eq(timestamp)
161
+ end
162
+ end
163
+
164
+ describe '#set_auth_header' do
165
+ it 'sets the auth header' do
166
+ driven_request.set_auth_header('APIAuth 1044:54321')
167
+ expect(request.headers['Authorization']).to eq('APIAuth 1044:54321')
168
+ end
169
+ end
170
+ end
171
+
172
+ describe 'md5_mismatch?' do
173
+ context 'when getting' do
174
+ let(:method) { 'get' }
175
+
176
+ it 'is false' do
177
+ expect(driven_request.md5_mismatch?).to be false
178
+ end
179
+ end
180
+
181
+ context 'when posting' do
182
+ let(:method) { 'post' }
183
+
184
+ context 'when calculated matches sent' do
185
+ it 'is false' do
186
+ expect(driven_request.md5_mismatch?).to be false
187
+ end
188
+ end
189
+
190
+ context "when calculated doesn't match sent" do
191
+ let(:params) { { 'message' => 'hello only' } }
192
+
193
+ it 'is true' do
194
+ expect(driven_request.md5_mismatch?).to be true
195
+ end
196
+ end
197
+ end
198
+
199
+ context 'when putting' do
200
+ let(:method) { 'put' }
201
+
202
+ context 'when calculated matches sent' do
203
+ it 'is false' do
204
+ expect(driven_request.md5_mismatch?).to be false
205
+ end
206
+ end
207
+
208
+ context "when calculated doesn't match sent" do
209
+ let(:params) { { 'message' => 'hello only' } }
210
+ it 'is true' do
211
+ expect(driven_request.md5_mismatch?).to be true
212
+ end
213
+ end
214
+ end
215
+
216
+ context 'when deleting' do
217
+ let(:method) { 'delete' }
218
+
219
+ it 'is false' do
220
+ expect(driven_request.md5_mismatch?).to be false
221
+ end
222
+ end
223
+ end
224
+
225
+ describe 'authentics?' do
226
+ let(:request_headers) { {} }
227
+ let(:signed_request) do
228
+ ApiAuth.sign!(request, '1044', '123')
229
+ end
230
+
231
+ context 'when getting' do
232
+ let(:method) { 'get' }
233
+
234
+ it 'validates that the signature in the request header matches the way we sign it' do
235
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
236
+ end
237
+ end
238
+
239
+ context 'when posting' do
240
+ let(:method) { 'post' }
241
+
242
+ it 'validates that the signature in the request header matches the way we sign it' do
243
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
244
+ end
245
+ end
246
+
247
+ context 'when putting' do
248
+ let(:method) { 'put' }
249
+
250
+ let(:signed_request) do
251
+ ApiAuth.sign!(request, '1044', '123')
252
+ end
253
+
254
+ it 'validates that the signature in the request header matches the way we sign it' do
255
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
256
+ end
257
+ end
258
+
259
+ context 'when deleting' do
260
+ let(:method) { 'delete' }
261
+
262
+ let(:signed_request) do
263
+ ApiAuth.sign!(request, '1044', '123')
264
+ end
265
+
266
+ it 'validates that the signature in the request header matches the way we sign it' do
267
+ expect(ApiAuth.authentic?(signed_request, '123')).to eq true
268
+ end
269
+ end
270
+ end
271
+ end
@@ -16,6 +16,7 @@ require 'curb'
16
16
  require 'http'
17
17
  require 'httpi'
18
18
  require 'faraday'
19
+ require 'grape'
19
20
  require 'net/http/post/multipart'
20
21
 
21
22
  # Requires supporting files with custom matchers and macros, etc,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mauricio Gomes
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-12 00:00:00.000000000 Z
11
+ date: 2018-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -204,6 +204,20 @@ dependencies:
204
204
  - - "~>"
205
205
  - !ruby/object:Gem::Version
206
206
  version: '2.0'
207
+ - !ruby/object:Gem::Dependency
208
+ name: grape
209
+ requirement: !ruby/object:Gem::Requirement
210
+ requirements:
211
+ - - "~>"
212
+ - !ruby/object:Gem::Version
213
+ version: 1.1.0
214
+ type: :development
215
+ prerelease: false
216
+ version_requirements: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - "~>"
219
+ - !ruby/object:Gem::Version
220
+ version: 1.1.0
207
221
  - !ruby/object:Gem::Dependency
208
222
  name: rspec
209
223
  requirement: !ruby/object:Gem::Requirement
@@ -246,6 +260,7 @@ files:
246
260
  - gemfiles/rails_42.gemfile
247
261
  - gemfiles/rails_5.gemfile
248
262
  - gemfiles/rails_51.gemfile
263
+ - gemfiles/rails_52.gemfile
249
264
  - lib/api-auth.rb
250
265
  - lib/api_auth.rb
251
266
  - lib/api_auth/base.rb
@@ -257,6 +272,7 @@ files:
257
272
  - lib/api_auth/request_drivers/action_dispatch.rb
258
273
  - lib/api_auth/request_drivers/curb.rb
259
274
  - lib/api_auth/request_drivers/faraday.rb
275
+ - lib/api_auth/request_drivers/grape_request.rb
260
276
  - lib/api_auth/request_drivers/http.rb
261
277
  - lib/api_auth/request_drivers/httpi.rb
262
278
  - lib/api_auth/request_drivers/net_http.rb
@@ -272,6 +288,7 @@ files:
272
288
  - spec/request_drivers/action_dispatch_spec.rb
273
289
  - spec/request_drivers/curb_spec.rb
274
290
  - spec/request_drivers/faraday_spec.rb
291
+ - spec/request_drivers/grape_request_spec.rb
275
292
  - spec/request_drivers/http_spec.rb
276
293
  - spec/request_drivers/httpi_spec.rb
277
294
  - spec/request_drivers/net_http_spec.rb
@@ -289,7 +306,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
289
306
  requirements:
290
307
  - - ">="
291
308
  - !ruby/object:Gem::Version
292
- version: 2.1.0
309
+ version: 2.3.0
293
310
  required_rubygems_version: !ruby/object:Gem::Requirement
294
311
  requirements:
295
312
  - - ">="
@@ -297,7 +314,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
314
  version: '0'
298
315
  requirements: []
299
316
  rubyforge_project:
300
- rubygems_version: 2.7.3
317
+ rubygems_version: 2.7.7
301
318
  signing_key:
302
319
  specification_version: 4
303
320
  summary: Simple HMAC authentication for your APIs
@@ -311,6 +328,7 @@ test_files:
311
328
  - spec/request_drivers/action_dispatch_spec.rb
312
329
  - spec/request_drivers/curb_spec.rb
313
330
  - spec/request_drivers/faraday_spec.rb
331
+ - spec/request_drivers/grape_request_spec.rb
314
332
  - spec/request_drivers/http_spec.rb
315
333
  - spec/request_drivers/httpi_spec.rb
316
334
  - spec/request_drivers/net_http_spec.rb