mauth-client 5.0.2 → 6.2.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/.gitignore +4 -0
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/.travis.yml +20 -11
- data/Appraisals +7 -0
- data/CHANGELOG.md +20 -0
- data/CONTRIBUTING.md +17 -0
- data/doc/implementations.md +2 -3
- data/doc/mauth.yml.md +11 -0
- data/examples/Gemfile +1 -1
- data/examples/Gemfile.lock +24 -18
- data/examples/config.yml +4 -0
- data/gemfiles/faraday_0.x.gemfile +7 -0
- data/gemfiles/faraday_1.x.gemfile +7 -0
- data/lib/mauth/client.rb +14 -0
- data/lib/mauth/client/authenticator_base.rb +14 -2
- data/lib/mauth/client/remote_authenticator.rb +1 -1
- data/lib/mauth/client/security_token_cacher.rb +17 -20
- data/lib/mauth/client/signer.rb +2 -0
- data/lib/mauth/dice_bag/mauth.yml.dice +2 -0
- data/lib/mauth/request_and_response.rb +47 -21
- data/lib/mauth/version.rb +3 -1
- data/mauth-client.gemspec +6 -2
- metadata +88 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be045713042cd9a25e7b0219d959c1208c66b700c8a113130d7865d91e17db7d
|
|
4
|
+
data.tar.gz: 155e903f0fa2fafabf2167c9d908398259ec2c9336b7f953bda83efe13667559
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8c97d131c0f9aef554ab5ebb7fd06193ec50a50f7a81fc93a184ba30b5b91bf870c560a06ce9c5dfeb19dab7cb7410c771b355fbbfb1055e56327147c32c5a6a
|
|
7
|
+
data.tar.gz: 446f8d0a681376361e3b9eabd86b20f3bfe2d28bdb028edf0b78c6e21f9222a7eaed5de3ba4fecd5afa95059698a14f6f4aaeba083d31ff5c55f618b9cbcdb12
|
data/.gitignore
CHANGED
data/.gitmodules
ADDED
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color
|
data/.travis.yml
CHANGED
|
@@ -2,24 +2,33 @@ language: ruby
|
|
|
2
2
|
cache: bundler
|
|
3
3
|
|
|
4
4
|
rvm:
|
|
5
|
-
- 2.3
|
|
6
|
-
- 2.4
|
|
7
|
-
- 2.5
|
|
8
|
-
- 2.6
|
|
5
|
+
- 2.3
|
|
6
|
+
- 2.4
|
|
7
|
+
- 2.5
|
|
8
|
+
- 2.6
|
|
9
|
+
- 2.7
|
|
10
|
+
|
|
11
|
+
gemfile:
|
|
12
|
+
- gemfiles/faraday_0.x.gemfile
|
|
13
|
+
- gemfiles/faraday_1.x.gemfile
|
|
9
14
|
|
|
10
15
|
before_install:
|
|
11
|
-
- gem update --system -N > /dev/null && echo "Rubygems version $(gem --version)"
|
|
12
|
-
- gem install bundler --force -N -v=2.
|
|
16
|
+
- gem update --system --force -N > /dev/null && echo "Rubygems version $(gem --version)"
|
|
17
|
+
- gem install bundler --force -N -v=2.1.4 && bundle --version
|
|
13
18
|
|
|
14
19
|
install:
|
|
15
20
|
- bundle install --jobs=3 --retry=3
|
|
16
|
-
-
|
|
17
|
-
curl -H 'Cache-Control: no-cache'
|
|
21
|
+
- >-
|
|
22
|
+
curl -H 'Cache-Control: no-cache'
|
|
23
|
+
https://raw.githubusercontent.com/mdsol/fossa_ci_scripts/master/travis_ci/fossa_install.sh |
|
|
24
|
+
bash -s -- -b $TRAVIS_BUILD_DIR
|
|
18
25
|
|
|
19
26
|
script:
|
|
20
27
|
- bundle exec rspec
|
|
21
|
-
-
|
|
22
|
-
curl -H 'Cache-Control: no-cache'
|
|
28
|
+
- >-
|
|
29
|
+
curl -H 'Cache-Control: no-cache'
|
|
30
|
+
https://raw.githubusercontent.com/mdsol/fossa_ci_scripts/master/travis_ci/fossa_run.sh |
|
|
31
|
+
bash -s -- -b $TRAVIS_BUILD_DIR
|
|
23
32
|
|
|
24
33
|
deploy:
|
|
25
34
|
provider: rubygems
|
|
@@ -29,4 +38,4 @@ deploy:
|
|
|
29
38
|
on:
|
|
30
39
|
tags: true
|
|
31
40
|
repo: mdsol/mauth-client-ruby
|
|
32
|
-
condition:
|
|
41
|
+
condition: $TRAVIS_RUBY_VERSION == 2.7 && $BUNDLE_GEMFILE == $TRAVIS_BUILD_DIR/gemfiles/faraday_1.x.gemfile
|
data/Appraisals
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## v6.2.0
|
|
2
|
+
* Drop legacy security token expiry in favor of honoring server cache headers via Faraday HTTP Cache Middleware.
|
|
3
|
+
|
|
4
|
+
## v6.1.1
|
|
5
|
+
* Replace `URI.escape` with `CGI.escape` in SecurityTokenCacher to suppress "URI.escape is obsolete" warning.
|
|
6
|
+
|
|
7
|
+
## v6.1.0
|
|
8
|
+
* Allow Faraday 1.x.
|
|
9
|
+
|
|
10
|
+
## v6.0.0
|
|
11
|
+
- Added parsing code to test with mauth-protocol-test-suite.
|
|
12
|
+
- Added unescape step in query_string encoding in order to remove 'double encoding'.
|
|
13
|
+
- Added normalization of paths.
|
|
14
|
+
- Added flag to sign only with V1.
|
|
15
|
+
- Changed V2 to V1 fallback to be configurable.
|
|
16
|
+
- Fixed bug in sorting query parameters.
|
|
17
|
+
|
|
18
|
+
## v5.1.0
|
|
19
|
+
- Fall back to V1 when V2 authentication fails.
|
|
20
|
+
|
|
1
21
|
## v5.0.2
|
|
2
22
|
- Fix to not raise FrozenError when string to sign contains frozen value.
|
|
3
23
|
|
data/CONTRIBUTING.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Contributing
|
|
2
2
|
|
|
3
|
+
## Cloning the Repo
|
|
4
|
+
|
|
5
|
+
This repo contains the submodule `mauth-protocol-test-suite` so requires a flag when initially cloning in order to clone and init submodules.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
git clone --recurse-submodules git@github.com:mdsol/mauth-client-ruby.git
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
If you have already cloned a version of this repo before the submodule was introduced in version 6.1.2 then run
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
cd spec/fixtures/mauth-protocol-test-suite
|
|
15
|
+
git submodule update --init
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
to init the submodule.
|
|
19
|
+
|
|
3
20
|
## General Information
|
|
4
21
|
|
|
5
22
|
* Check out the latest develop to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
data/doc/implementations.md
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
- Clojure: [clojure-mauth-client](https://github.com/mdsol/clojure-mauth-client)
|
|
5
5
|
- Go: [go-mauth-client](https://github.com/mdsol/go-mauth-client)
|
|
6
6
|
- Java: [mauth-jvm-clients](https://github.com/mdsol/mauth-jvm-clients)
|
|
7
|
-
- Python:
|
|
8
|
-
- [requests-mauth](https://github.com/mdsol/requests-mauth)
|
|
9
|
-
- [flask-mauth](https://github.com/mdsol/flask-mauth)
|
|
7
|
+
- Python: [mauth-client-python](https://github.com/mdsol/mauth-client-python)
|
|
10
8
|
- R: [RMauthClient](https://github.com/mdsol/RMauthClient)
|
|
11
9
|
- Ruby: [mauth-client-ruby](https://github.com/mdsol/mauth-client-ruby)
|
|
10
|
+
- Rust: [mauth-client-rust](https://github.com/mdsol/mauth-client-rust)
|
data/doc/mauth.yml.md
CHANGED
|
@@ -29,6 +29,9 @@ common: &common
|
|
|
29
29
|
SIY2exfsy7Y8NoOnBPlGiXKhgaF21T8kqV9C7R6OAuP0U6CgMJnINx/UjozvBENH
|
|
30
30
|
Ux45QdvRd6vai8nHp7AgV7rr55SxXAZVgATll84uBUpfpmC6YK/j
|
|
31
31
|
-----END RSA PRIVATE KEY-----
|
|
32
|
+
v2_only_authenticate: false
|
|
33
|
+
v2_only_sign_requests: false
|
|
34
|
+
disable_fallback_to_v1_on_v2_failure: true
|
|
32
35
|
|
|
33
36
|
production:
|
|
34
37
|
<<: *common
|
|
@@ -46,6 +49,10 @@ common: &common
|
|
|
46
49
|
mauth_api_version: v1
|
|
47
50
|
app_uuid: 123we997-0333-44d8-8fCf-5dd555c5bd51
|
|
48
51
|
private_key_file: config/my_mauth_private.key
|
|
52
|
+
v2_only_authenticate: false
|
|
53
|
+
v2_only_sign_requests: false
|
|
54
|
+
disable_fallback_to_v1_on_v2_failure: true
|
|
55
|
+
v1_only_sign_requests: false
|
|
49
56
|
|
|
50
57
|
production:
|
|
51
58
|
<<: *common
|
|
@@ -62,6 +69,10 @@ test:
|
|
|
62
69
|
- `app_uuid` - Required in the same circumstances where a `private_key` is required.
|
|
63
70
|
- `mauth_baseurl` - Required for authentication but not for signing. Needed for local authentication to retrieve public keys and for remote authentication. Usually this is `https://mauth.imedidata.com` for production.
|
|
64
71
|
- `mauth_api_version` - Required for authentication but not for signing. only `v1` exists as of this writing.
|
|
72
|
+
- `v2_only_sign_requests` - If true, all outgoing requests will be signed with only the V2 protocol. Defaults to false.
|
|
73
|
+
- `v2_only_authenticate` - If true, any incoming request or incoming response that does not use the V2 protocol will be rejected. Defaults to false.
|
|
74
|
+
- `disable_fallback_to_v1_on_v2_failure` - If true, any incoming V2 requests that fail authentication will not fall back to V1 authentication. Defaults to false.
|
|
75
|
+
- `v1_only_sign_requests` - If true, all outgoing requests will be signed with only the V1 protocol. Defaults to true. Note, cannot be `true` if `v2_only_sign_requests` is also `true`.
|
|
65
76
|
|
|
66
77
|
## Usage in your application
|
|
67
78
|
|
data/examples/Gemfile
CHANGED
data/examples/Gemfile.lock
CHANGED
|
@@ -1,41 +1,47 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ..
|
|
3
3
|
specs:
|
|
4
|
-
mauth-client (
|
|
4
|
+
mauth-client (6.1.0)
|
|
5
|
+
addressable (~> 2.0)
|
|
5
6
|
coderay (~> 1.0)
|
|
6
7
|
dice_bag (>= 0.9, < 2.0)
|
|
7
|
-
faraday (
|
|
8
|
-
faraday_middleware (
|
|
8
|
+
faraday (>= 0.9, < 2.0)
|
|
9
|
+
faraday_middleware (>= 0.9, < 2.0)
|
|
9
10
|
rack
|
|
10
11
|
term-ansicolor (~> 1.0)
|
|
11
12
|
|
|
12
13
|
GEM
|
|
13
14
|
remote: https://rubygems.org/
|
|
14
15
|
specs:
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
addressable (2.7.0)
|
|
17
|
+
public_suffix (>= 2.0.2, < 5.0)
|
|
18
|
+
coderay (1.1.3)
|
|
19
|
+
dice_bag (1.4.1)
|
|
17
20
|
diff-lcs (~> 1.0)
|
|
18
21
|
rake
|
|
19
|
-
thor (
|
|
20
|
-
diff-lcs (1.
|
|
21
|
-
faraday (0.
|
|
22
|
+
thor (< 2.0)
|
|
23
|
+
diff-lcs (1.4.4)
|
|
24
|
+
faraday (1.0.1)
|
|
22
25
|
multipart-post (>= 1.2, < 3)
|
|
23
|
-
faraday_middleware (0.
|
|
24
|
-
faraday (
|
|
25
|
-
multipart-post (2.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
faraday_middleware (1.0.0)
|
|
27
|
+
faraday (~> 1.0)
|
|
28
|
+
multipart-post (2.1.1)
|
|
29
|
+
public_suffix (4.0.6)
|
|
30
|
+
rack (2.2.3)
|
|
31
|
+
rake (13.0.1)
|
|
32
|
+
sync (0.5.0)
|
|
33
|
+
term-ansicolor (1.7.1)
|
|
29
34
|
tins (~> 1.0)
|
|
30
|
-
thor (0.
|
|
31
|
-
tins (1.
|
|
35
|
+
thor (1.0.1)
|
|
36
|
+
tins (1.25.0)
|
|
37
|
+
sync
|
|
32
38
|
|
|
33
39
|
PLATFORMS
|
|
34
40
|
ruby
|
|
35
41
|
|
|
36
42
|
DEPENDENCIES
|
|
37
|
-
faraday (~> 0
|
|
43
|
+
faraday (~> 1.0)
|
|
38
44
|
mauth-client!
|
|
39
45
|
|
|
40
46
|
BUNDLED WITH
|
|
41
|
-
1.
|
|
47
|
+
2.1.4
|
data/examples/config.yml
CHANGED
data/lib/mauth/client.rb
CHANGED
|
@@ -153,6 +153,12 @@ module MAuth
|
|
|
153
153
|
@config['ssl_certs_path'] = given_config['ssl_certs_path'] if given_config['ssl_certs_path']
|
|
154
154
|
@config['v2_only_authenticate'] = given_config['v2_only_authenticate'].to_s.downcase == 'true'
|
|
155
155
|
@config['v2_only_sign_requests'] = given_config['v2_only_sign_requests'].to_s.downcase == 'true'
|
|
156
|
+
@config['disable_fallback_to_v1_on_v2_failure'] = given_config['disable_fallback_to_v1_on_v2_failure'].to_s.downcase == 'true'
|
|
157
|
+
@config['v1_only_sign_requests'] = given_config['v1_only_sign_requests'].to_s.downcase == 'true'
|
|
158
|
+
|
|
159
|
+
if @config['v2_only_sign_requests'] && @config['v1_only_sign_requests']
|
|
160
|
+
raise MAuth::Client::ConfigurationError, "v2_only_sign_requests and v1_only_sign_requests may not both be true"
|
|
161
|
+
end
|
|
156
162
|
|
|
157
163
|
# if 'authenticator' was given, don't override that - including if it was given as nil / false
|
|
158
164
|
if given_config.key?('authenticator')
|
|
@@ -205,6 +211,14 @@ module MAuth
|
|
|
205
211
|
@config['v2_only_authenticate']
|
|
206
212
|
end
|
|
207
213
|
|
|
214
|
+
def disable_fallback_to_v1_on_v2_failure?
|
|
215
|
+
@config['disable_fallback_to_v1_on_v2_failure']
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def v1_only_sign_requests?
|
|
219
|
+
@config['v1_only_sign_requests']
|
|
220
|
+
end
|
|
221
|
+
|
|
208
222
|
def assert_private_key(err)
|
|
209
223
|
raise err unless private_key
|
|
210
224
|
end
|
|
@@ -19,10 +19,22 @@ module MAuth
|
|
|
19
19
|
|
|
20
20
|
# raises InauthenticError unless the given object is authentic. Will only
|
|
21
21
|
# authenticate with v2 if the environment variable V2_ONLY_AUTHENTICATE
|
|
22
|
-
# is set. Otherwise will
|
|
22
|
+
# is set. Otherwise will fall back to v1 when v2 authentication fails
|
|
23
23
|
def authenticate!(object)
|
|
24
24
|
if object.protocol_version == 2
|
|
25
|
-
|
|
25
|
+
begin
|
|
26
|
+
authenticate_v2!(object)
|
|
27
|
+
rescue InauthenticError => e
|
|
28
|
+
raise e if v2_only_authenticate?
|
|
29
|
+
raise e if disable_fallback_to_v1_on_v2_failure?
|
|
30
|
+
|
|
31
|
+
object.fall_back_to_mws_signature_info
|
|
32
|
+
raise e unless object.signature
|
|
33
|
+
|
|
34
|
+
log_authentication_request(object)
|
|
35
|
+
authenticate_v1!(object)
|
|
36
|
+
logger.warn("Completed successful authentication attempt after fallback to v1")
|
|
37
|
+
end
|
|
26
38
|
elsif object.protocol_version == 1
|
|
27
39
|
if v2_only_authenticate?
|
|
28
40
|
# If v2 is required but not present and v1 is present we raise MissingV2Error
|
|
@@ -42,7 +42,7 @@ module MAuth
|
|
|
42
42
|
def make_mauth_request(authentication_ticket)
|
|
43
43
|
begin
|
|
44
44
|
response = mauth_connection.post("/mauth/#{mauth_api_version}/authentication_tickets.json", 'authentication_ticket' => authentication_ticket)
|
|
45
|
-
rescue ::Faraday::
|
|
45
|
+
rescue ::Faraday::ConnectionFailed, ::Faraday::TimeoutError => e
|
|
46
46
|
msg = "mAuth service did not respond; received #{e.class}: #{e.message}"
|
|
47
47
|
logger.error("Unable to authenticate with MAuth. Exception #{msg}")
|
|
48
48
|
raise UnableToAuthenticateError, msg
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
+
require 'faraday-http-cache'
|
|
2
|
+
require 'oj'
|
|
3
|
+
|
|
1
4
|
module MAuth
|
|
2
5
|
class Client
|
|
3
6
|
module LocalAuthenticator
|
|
4
7
|
class SecurityTokenCacher
|
|
5
8
|
|
|
6
|
-
class ExpirableSecurityToken < Struct.new(:security_token, :create_time)
|
|
7
|
-
CACHE_LIFE = 60
|
|
8
|
-
def expired?
|
|
9
|
-
create_time + CACHE_LIFE < Time.now
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
9
|
def initialize(mauth_client)
|
|
14
10
|
@mauth_client = mauth_client
|
|
15
11
|
# TODO: should this be UnableToSignError?
|
|
@@ -20,28 +16,20 @@ module MAuth
|
|
|
20
16
|
end
|
|
21
17
|
|
|
22
18
|
def get(app_uuid)
|
|
23
|
-
if !@cache[app_uuid]
|
|
19
|
+
if !@cache[app_uuid]
|
|
24
20
|
# url-encode the app_uuid to prevent trickery like escaping upward with ../../ in a malicious
|
|
25
21
|
# app_uuid - probably not exploitable, but this is the right way to do it anyway.
|
|
26
|
-
|
|
27
|
-
url_encoded_app_uuid = URI.escape(app_uuid, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
|
22
|
+
url_encoded_app_uuid = CGI.escape(app_uuid)
|
|
28
23
|
begin
|
|
29
24
|
response = signed_mauth_connection.get("/mauth/#{@mauth_client.mauth_api_version}/security_tokens/#{url_encoded_app_uuid}.json")
|
|
30
|
-
rescue ::Faraday::
|
|
25
|
+
rescue ::Faraday::ConnectionFailed, ::Faraday::TimeoutError => e
|
|
31
26
|
msg = "mAuth service did not respond; received #{e.class}: #{e.message}"
|
|
32
27
|
@mauth_client.logger.error("Unable to authenticate with MAuth. Exception #{msg}")
|
|
33
28
|
raise UnableToAuthenticateError, msg
|
|
34
29
|
end
|
|
35
30
|
if response.status == 200
|
|
36
|
-
begin
|
|
37
|
-
security_token = JSON.parse(response.body)
|
|
38
|
-
rescue JSON::ParserError => e
|
|
39
|
-
msg = "mAuth service responded with unparseable json: #{response.body}\n#{e.class}: #{e.message}"
|
|
40
|
-
@mauth_client.logger.error("Unable to authenticate with MAuth. Exception #{msg}")
|
|
41
|
-
raise UnableToAuthenticateError, msg
|
|
42
|
-
end
|
|
43
31
|
@cache_write_lock.synchronize do
|
|
44
|
-
@cache[app_uuid] =
|
|
32
|
+
@cache[app_uuid] = security_token_from(response.body)
|
|
45
33
|
end
|
|
46
34
|
elsif response.status == 404
|
|
47
35
|
# signing with a key mAuth doesn't know about is considered inauthentic
|
|
@@ -50,11 +38,19 @@ module MAuth
|
|
|
50
38
|
@mauth_client.send(:mauth_service_response_error, response)
|
|
51
39
|
end
|
|
52
40
|
end
|
|
53
|
-
@cache[app_uuid]
|
|
41
|
+
@cache[app_uuid]
|
|
54
42
|
end
|
|
55
43
|
|
|
56
44
|
private
|
|
57
45
|
|
|
46
|
+
def security_token_from(response_body)
|
|
47
|
+
JSON.parse response_body
|
|
48
|
+
rescue JSON::ParserError => e
|
|
49
|
+
msg = "mAuth service responded with unparseable json: #{response_body}\n#{e.class}: #{e.message}"
|
|
50
|
+
@mauth_client.logger.error("Unable to authenticate with MAuth. Exception #{msg}")
|
|
51
|
+
raise UnableToAuthenticateError, msg
|
|
52
|
+
end
|
|
53
|
+
|
|
58
54
|
def signed_mauth_connection
|
|
59
55
|
require 'faraday'
|
|
60
56
|
require 'mauth/faraday'
|
|
@@ -62,6 +58,7 @@ module MAuth
|
|
|
62
58
|
@signed_mauth_connection ||= ::Faraday.new(@mauth_client.mauth_baseurl, @mauth_client.faraday_options) do |builder|
|
|
63
59
|
builder.use MAuth::Faraday::MAuthClientUserAgent
|
|
64
60
|
builder.use MAuth::Faraday::RequestSigner, 'mauth_client' => @mauth_client
|
|
61
|
+
builder.use :http_cache, serializer: Oj, logger: MAuth::Client.new.logger, shared_cache: false
|
|
65
62
|
builder.adapter ::Faraday.default_adapter
|
|
66
63
|
end
|
|
67
64
|
end
|
data/lib/mauth/client/signer.rb
CHANGED
|
@@ -30,6 +30,8 @@ module MAuth
|
|
|
30
30
|
def signed_headers(object, attributes = {})
|
|
31
31
|
if v2_only_sign_requests?
|
|
32
32
|
signed_headers_v2(object, attributes)
|
|
33
|
+
elsif v1_only_sign_requests?
|
|
34
|
+
signed_headers_v1(object, attributes)
|
|
33
35
|
else # by default sign with both the v1 and v2 protocol
|
|
34
36
|
signed_headers_v1(object, attributes).merge(signed_headers_v2(object, attributes))
|
|
35
37
|
end
|
|
@@ -7,6 +7,8 @@ common: &common
|
|
|
7
7
|
private_key_file: config/mauth_key
|
|
8
8
|
v2_only_authenticate: <%= configured.v2_only_authenticate || 'false' %>
|
|
9
9
|
v2_only_sign_requests: <%= configured.v2_only_sign_requests || 'false' %>
|
|
10
|
+
disable_fallback_to_v1_on_v2_failure: <%= configured.disable_fallback_to_v1_on_v2_failure || 'false' %>
|
|
11
|
+
v1_only_sign_requests: <%= configured.v1_only_sign_requests || 'true' %>
|
|
10
12
|
|
|
11
13
|
production:
|
|
12
14
|
<<: *common
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'digest'
|
|
2
|
+
require 'addressable'
|
|
2
3
|
|
|
3
4
|
module MAuth
|
|
4
5
|
# module which composes a string to sign.
|
|
@@ -25,7 +26,7 @@ module MAuth
|
|
|
25
26
|
# for responses:
|
|
26
27
|
# string_to_sign =
|
|
27
28
|
# status_code_string + <LF> +
|
|
28
|
-
#
|
|
29
|
+
# response_body + <LF> +
|
|
29
30
|
# app_uuid + <LF> +
|
|
30
31
|
# current_seconds_since_epoch
|
|
31
32
|
def string_to_sign_v1(more_attributes)
|
|
@@ -59,9 +60,10 @@ module MAuth
|
|
|
59
60
|
|
|
60
61
|
# memoization of body_digest to avoid hashing three times when we call
|
|
61
62
|
# string_to_sign_v2 three times in client#signature_valid_v2!
|
|
62
|
-
# note that if :body is nil we hash an empty string (
|
|
63
|
-
attrs_with_overrides[:body_digest] ||= Digest::SHA512.hexdigest(attrs_with_overrides[:body]
|
|
64
|
-
attrs_with_overrides[:encoded_query_params] =
|
|
63
|
+
# note that if :body is nil we hash an empty string ('')
|
|
64
|
+
attrs_with_overrides[:body_digest] ||= Digest::SHA512.hexdigest(attrs_with_overrides[:body] || '')
|
|
65
|
+
attrs_with_overrides[:encoded_query_params] = unescape_encode_query_string(attrs_with_overrides[:query_string] || '')
|
|
66
|
+
attrs_with_overrides[:request_url] = normalize_path(attrs_with_overrides[:request_url])
|
|
65
67
|
|
|
66
68
|
missing_attributes = self.class::SIGNATURE_COMPONENTS_V2.reject do |key|
|
|
67
69
|
attrs_with_overrides.dig(key)
|
|
@@ -78,12 +80,26 @@ module MAuth
|
|
|
78
80
|
end.join("\n")
|
|
79
81
|
end
|
|
80
82
|
|
|
83
|
+
# Addressable::URI.parse(path).normalize.to_s.squeeze('/')
|
|
84
|
+
def normalize_path(path)
|
|
85
|
+
return if path.nil?
|
|
86
|
+
|
|
87
|
+
# Addressable::URI.normalize_path normalizes `.` and `..` in path
|
|
88
|
+
# i.e. /./example => /example ; /example/.. => /
|
|
89
|
+
# String#squeeze removes duplicated slahes i.e. /// => /
|
|
90
|
+
# String#gsub normalizes percent encoding to uppercase i.e. %cf%80 => %CF%80
|
|
91
|
+
Addressable::URI.normalize_path(path).squeeze('/').
|
|
92
|
+
gsub(/%[a-f0-9]{2}/, &:upcase)
|
|
93
|
+
end
|
|
94
|
+
|
|
81
95
|
# sorts query string parameters by codepoint, uri encodes keys and values,
|
|
82
96
|
# and rejoins parameters into a query string
|
|
83
|
-
def
|
|
84
|
-
q_string.split('&').
|
|
85
|
-
k,
|
|
86
|
-
|
|
97
|
+
def unescape_encode_query_string(q_string)
|
|
98
|
+
fir = q_string.split('&').map do |part|
|
|
99
|
+
k, _eq, v = part.partition('=')
|
|
100
|
+
[CGI.unescape(k), CGI.unescape(v)]
|
|
101
|
+
end.sort.map do |k, v|
|
|
102
|
+
"#{uri_escape(k)}=#{uri_escape(v)}"
|
|
87
103
|
end.join('&')
|
|
88
104
|
end
|
|
89
105
|
|
|
@@ -115,23 +131,17 @@ module MAuth
|
|
|
115
131
|
# - #x_mws_authentication which returns that header's value
|
|
116
132
|
# - #x_mws_time
|
|
117
133
|
module Signed
|
|
118
|
-
# mauth_client will authenticate with the highest protocol version present and
|
|
119
|
-
# protocol versions.
|
|
134
|
+
# mauth_client will authenticate with the highest protocol version present and if authentication fails,
|
|
135
|
+
# will fall back to lower protocol versions (if provided).
|
|
120
136
|
# returns a hash with keys :token, :app_uuid, and :signature parsed from the MCC-Authentication header
|
|
121
137
|
# if it is present and if not then the X-MWS-Authentication header if it is present.
|
|
122
138
|
# Note MWSV2 protocol no longer allows more than one space between the token and app uuid.
|
|
123
139
|
def signature_info
|
|
124
|
-
@signature_info ||=
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
elsif x_mws_authentication
|
|
130
|
-
x_mws_authentication.match(/\A([^ ]+) *([^:]+):([^:]+)\z/)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
match ? { token: match[1], app_uuid: match[2], signature: match[3] } : {}
|
|
134
|
-
end
|
|
140
|
+
@signature_info ||= build_signature_info(mcc_data || x_mws_data)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def fall_back_to_mws_signature_info
|
|
144
|
+
@signature_info = build_signature_info(x_mws_data)
|
|
135
145
|
end
|
|
136
146
|
|
|
137
147
|
def signature_app_uuid
|
|
@@ -153,6 +163,22 @@ module MAuth
|
|
|
153
163
|
1
|
|
154
164
|
end
|
|
155
165
|
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def build_signature_info(match_data)
|
|
170
|
+
match_data ? { token: match_data[1], app_uuid: match_data[2], signature: match_data[3] } : {}
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def mcc_data
|
|
174
|
+
mcc_authentication&.match(
|
|
175
|
+
/\A(#{MAuth::Client::MWSV2_TOKEN}) ([^:]+):([^:]+)#{MAuth::Client::AUTH_HEADER_DELIMITER}\z/
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def x_mws_data
|
|
180
|
+
x_mws_authentication&.match(/\A([^ ]+) *([^:]+):([^:]+)\z/)
|
|
181
|
+
end
|
|
156
182
|
end
|
|
157
183
|
|
|
158
184
|
# virtual base class for signable requests
|
data/lib/mauth/version.rb
CHANGED
data/mauth-client.gemspec
CHANGED
|
@@ -18,13 +18,17 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
19
|
spec.require_paths = ['lib']
|
|
20
20
|
|
|
21
|
-
spec.add_dependency 'faraday', '
|
|
22
|
-
spec.add_dependency 'faraday_middleware', '
|
|
21
|
+
spec.add_dependency 'faraday', '>= 0.9', '< 2.0'
|
|
22
|
+
spec.add_dependency 'faraday_middleware', '>= 0.9', '< 2.0'
|
|
23
|
+
spec.add_dependency 'faraday-http-cache', '>= 2.0', '< 3.0'
|
|
24
|
+
spec.add_dependency 'oj', '~> 3.0'
|
|
23
25
|
spec.add_dependency 'term-ansicolor', '~> 1.0'
|
|
24
26
|
spec.add_dependency 'coderay', '~> 1.0'
|
|
25
27
|
spec.add_dependency 'rack'
|
|
26
28
|
spec.add_dependency 'dice_bag', '>= 0.9', '< 2.0'
|
|
29
|
+
spec.add_dependency 'addressable', '~> 2.0'
|
|
27
30
|
|
|
31
|
+
spec.add_development_dependency 'appraisal'
|
|
28
32
|
spec.add_development_dependency 'bundler', '>= 1.17'
|
|
29
33
|
spec.add_development_dependency 'byebug'
|
|
30
34
|
spec.add_development_dependency 'rack-test', '~> 1.1.0'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mauth-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthew Szenher
|
|
@@ -11,36 +11,82 @@ authors:
|
|
|
11
11
|
autorequire:
|
|
12
12
|
bindir: exe
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date:
|
|
14
|
+
date: 2021-06-10 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: faraday
|
|
18
18
|
requirement: !ruby/object:Gem::Requirement
|
|
19
19
|
requirements:
|
|
20
|
-
- - "
|
|
20
|
+
- - ">="
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '0.9'
|
|
23
|
+
- - "<"
|
|
21
24
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: '0
|
|
25
|
+
version: '2.0'
|
|
23
26
|
type: :runtime
|
|
24
27
|
prerelease: false
|
|
25
28
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
29
|
requirements:
|
|
27
|
-
- - "
|
|
30
|
+
- - ">="
|
|
28
31
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '0.
|
|
32
|
+
version: '0.9'
|
|
33
|
+
- - "<"
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '2.0'
|
|
30
36
|
- !ruby/object:Gem::Dependency
|
|
31
37
|
name: faraday_middleware
|
|
32
38
|
requirement: !ruby/object:Gem::Requirement
|
|
33
39
|
requirements:
|
|
34
|
-
- - "
|
|
40
|
+
- - ">="
|
|
35
41
|
- !ruby/object:Gem::Version
|
|
36
42
|
version: '0.9'
|
|
43
|
+
- - "<"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '2.0'
|
|
37
46
|
type: :runtime
|
|
38
47
|
prerelease: false
|
|
39
48
|
version_requirements: !ruby/object:Gem::Requirement
|
|
40
49
|
requirements:
|
|
41
|
-
- - "
|
|
50
|
+
- - ">="
|
|
42
51
|
- !ruby/object:Gem::Version
|
|
43
52
|
version: '0.9'
|
|
53
|
+
- - "<"
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '2.0'
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: faraday-http-cache
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '2.0'
|
|
63
|
+
- - "<"
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '3.0'
|
|
66
|
+
type: :runtime
|
|
67
|
+
prerelease: false
|
|
68
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '2.0'
|
|
73
|
+
- - "<"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.0'
|
|
76
|
+
- !ruby/object:Gem::Dependency
|
|
77
|
+
name: oj
|
|
78
|
+
requirement: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.0'
|
|
83
|
+
type: :runtime
|
|
84
|
+
prerelease: false
|
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '3.0'
|
|
44
90
|
- !ruby/object:Gem::Dependency
|
|
45
91
|
name: term-ansicolor
|
|
46
92
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -103,6 +149,34 @@ dependencies:
|
|
|
103
149
|
- - "<"
|
|
104
150
|
- !ruby/object:Gem::Version
|
|
105
151
|
version: '2.0'
|
|
152
|
+
- !ruby/object:Gem::Dependency
|
|
153
|
+
name: addressable
|
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - "~>"
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '2.0'
|
|
159
|
+
type: :runtime
|
|
160
|
+
prerelease: false
|
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - "~>"
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '2.0'
|
|
166
|
+
- !ruby/object:Gem::Dependency
|
|
167
|
+
name: appraisal
|
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - ">="
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: '0'
|
|
173
|
+
type: :development
|
|
174
|
+
prerelease: false
|
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
176
|
+
requirements:
|
|
177
|
+
- - ">="
|
|
178
|
+
- !ruby/object:Gem::Version
|
|
179
|
+
version: '0'
|
|
106
180
|
- !ruby/object:Gem::Dependency
|
|
107
181
|
name: bundler
|
|
108
182
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -228,8 +302,11 @@ extra_rdoc_files: []
|
|
|
228
302
|
files:
|
|
229
303
|
- ".fossa.yml"
|
|
230
304
|
- ".gitignore"
|
|
305
|
+
- ".gitmodules"
|
|
306
|
+
- ".rspec"
|
|
231
307
|
- ".travis.yml"
|
|
232
308
|
- ".yardopts"
|
|
309
|
+
- Appraisals
|
|
233
310
|
- CHANGELOG.md
|
|
234
311
|
- CONTRIBUTING.md
|
|
235
312
|
- Gemfile
|
|
@@ -248,6 +325,8 @@ files:
|
|
|
248
325
|
- examples/mauth_key
|
|
249
326
|
- exe/mauth-client
|
|
250
327
|
- exe/mauth-proxy
|
|
328
|
+
- gemfiles/faraday_0.x.gemfile
|
|
329
|
+
- gemfiles/faraday_1.x.gemfile
|
|
251
330
|
- lib/mauth-client.rb
|
|
252
331
|
- lib/mauth/autoload.rb
|
|
253
332
|
- lib/mauth/client.rb
|
|
@@ -290,8 +369,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
290
369
|
- !ruby/object:Gem::Version
|
|
291
370
|
version: '0'
|
|
292
371
|
requirements: []
|
|
293
|
-
|
|
294
|
-
rubygems_version: 2.7.7
|
|
372
|
+
rubygems_version: 3.0.8
|
|
295
373
|
signing_key:
|
|
296
374
|
specification_version: 4
|
|
297
375
|
summary: Sign and authenticate requests and responses with mAuth authentication.
|