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 +4 -4
- data/.rubocop.yml +6 -0
- data/.rubocop_todo.yml +36 -4
- data/.travis.yml +3 -15
- data/CHANGELOG.md +17 -0
- data/README.md +10 -2
- data/VERSION +1 -1
- data/api_auth.gemspec +3 -2
- data/gemfiles/http2.gemfile +3 -3
- data/gemfiles/http3.gemfile +3 -3
- data/gemfiles/http4.gemfile +3 -3
- data/gemfiles/rails_4.gemfile +7 -7
- data/gemfiles/rails_41.gemfile +7 -7
- data/gemfiles/rails_42.gemfile +7 -7
- data/gemfiles/rails_5.gemfile +6 -6
- data/gemfiles/rails_51.gemfile +5 -5
- data/gemfiles/rails_52.gemfile +9 -0
- data/lib/api_auth.rb +1 -0
- data/lib/api_auth/base.rb +1 -1
- data/lib/api_auth/headers.rb +17 -6
- data/lib/api_auth/request_drivers/action_controller.rb +1 -0
- data/lib/api_auth/request_drivers/faraday.rb +2 -1
- data/lib/api_auth/request_drivers/grape_request.rb +87 -0
- data/lib/api_auth/request_drivers/http.rb +1 -0
- data/lib/api_auth/request_drivers/httpi.rb +1 -0
- data/lib/api_auth/request_drivers/net_http.rb +1 -0
- data/lib/api_auth/request_drivers/rack.rb +1 -0
- data/lib/api_auth/request_drivers/rest_client.rb +1 -0
- data/spec/api_auth_spec.rb +7 -0
- data/spec/headers_spec.rb +26 -0
- data/spec/request_drivers/grape_request_spec.rb +271 -0
- data/spec/spec_helper.rb +1 -0
- metadata +22 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92c94ba421b1f15829f9b16eb5bcbeb55c773298023134786011282639cba630
|
4
|
+
data.tar.gz: e14ce2ddf081464504b55a8aecd44afbc9526945a89c27e5a96d8bf500c1fa52
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d392d56ebd5dd7592a363d623c51fe00750d97fc212114d37d6c79a650bc3c0c7bfa020bf6e4c2f207f6c2dd71ac67cabf6affbbe28d4c7b6c1d757c67abc90a
|
7
|
+
data.tar.gz: f88c2d4d14dc61b5f9e867aecf4c3680e117ef6194617447963ddea1b6f93e5d3de7e414bc2da12e4c251d5803a4a2d28e91f5d817f29809aa7cc260d299af16
|
data/.rubocop.yml
CHANGED
@@ -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
|
data/.rubocop_todo.yml
CHANGED
@@ -1,50 +1,82 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2018-
|
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:
|
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:
|
45
|
+
Max: 15
|
30
46
|
|
31
47
|
# Offense count: 1
|
32
48
|
Metrics/PerceivedComplexity:
|
33
49
|
Max: 8
|
34
50
|
|
35
|
-
# Offense count:
|
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:
|
data/.travis.yml
CHANGED
@@ -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.
|
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.
|
29
|
+
- rvm: 2.5.3
|
42
30
|
gemfile: gemfiles/rails_5.gemfile
|
43
31
|
env: TEST_SUITE="rubocop lib/ spec/"
|
44
32
|
|
data/CHANGELOG.md
CHANGED
@@ -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
|
-
|
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.
|
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.
|
1
|
+
2.3.0
|
data/api_auth.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
$LOAD_PATH.push File.expand_path('
|
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.
|
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")
|
data/gemfiles/http2.gemfile
CHANGED
data/gemfiles/http3.gemfile
CHANGED
data/gemfiles/http4.gemfile
CHANGED
data/gemfiles/rails_4.gemfile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# This file was generated by Appraisal
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
8
|
-
gem
|
9
|
-
gem
|
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 :
|
11
|
+
gemspec path: '../'
|
data/gemfiles/rails_41.gemfile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# This file was generated by Appraisal
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
8
|
-
gem
|
9
|
-
gem
|
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 :
|
11
|
+
gemspec path: '../'
|
data/gemfiles/rails_42.gemfile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# This file was generated by Appraisal
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
8
|
-
gem
|
9
|
-
gem
|
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 :
|
11
|
+
gemspec path: '../'
|
data/gemfiles/rails_5.gemfile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# This file was generated by Appraisal
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
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
|
9
|
+
gem 'rubocop'
|
10
10
|
|
11
|
-
gemspec :
|
11
|
+
gemspec path: '../'
|
data/gemfiles/rails_51.gemfile
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# This file was generated by Appraisal
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
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 :
|
9
|
+
gemspec path: '../'
|
data/lib/api_auth.rb
CHANGED
@@ -9,6 +9,7 @@ require 'api_auth/request_drivers/net_http'
|
|
9
9
|
require 'api_auth/request_drivers/curb'
|
10
10
|
require 'api_auth/request_drivers/rest_client'
|
11
11
|
require 'api_auth/request_drivers/action_controller'
|
12
|
+
require 'api_auth/request_drivers/grape_request'
|
12
13
|
require 'api_auth/request_drivers/action_dispatch'
|
13
14
|
require 'api_auth/request_drivers/rack'
|
14
15
|
require 'api_auth/request_drivers/httpi'
|
data/lib/api_auth/base.rb
CHANGED
@@ -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
|
data/lib/api_auth/headers.rb
CHANGED
@@ -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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
@@ -16,12 +16,13 @@ module ApiAuth
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def calculated_md5
|
19
|
-
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
|
data/spec/api_auth_spec.rb
CHANGED
@@ -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
|
data/spec/headers_spec.rb
CHANGED
@@ -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
|
data/spec/spec_helper.rb
CHANGED
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.
|
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-
|
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.
|
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.
|
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
|