api-auth 2.2.1 → 2.3.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 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