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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f5fc7f62399a4835be8f3f8b8158c6cdac97e9cba3b6103aa4d714755a7b876
4
- data.tar.gz: e8c7414fff883d6d20a946b7486e7dc729435ef3ded2773db88a3be73ef137a3
3
+ metadata.gz: be045713042cd9a25e7b0219d959c1208c66b700c8a113130d7865d91e17db7d
4
+ data.tar.gz: 155e903f0fa2fafabf2167c9d908398259ec2c9336b7f953bda83efe13667559
5
5
  SHA512:
6
- metadata.gz: 89ba02ee21708b2c9db961ab1388282e1d0ebb5255360e12a02d64fa77c1b5aa39949a21da3e55c2c5f34e4fe74e0beb86231a138db6b308284ace48f254a2ca
7
- data.tar.gz: a6b343856370bec0e8e2a32dedc274209070262336ac23d7cf9cbaee7ae99a97741dfcfae4148825601b09e9381369aa0d825e801428aae1bdfc7ed5a0c2f9bb
6
+ metadata.gz: 8c97d131c0f9aef554ab5ebb7fd06193ec50a50f7a81fc93a184ba30b5b91bf870c560a06ce9c5dfeb19dab7cb7410c771b355fbbfb1055e56327147c32c5a6a
7
+ data.tar.gz: 446f8d0a681376361e3b9eabd86b20f3bfe2d28bdb028edf0b78c6e21f9222a7eaed5de3ba4fecd5afa95059698a14f6f4aaeba083d31ff5c55f618b9cbcdb12
data/.gitignore CHANGED
@@ -6,5 +6,9 @@
6
6
  /yardoc
7
7
  /log
8
8
 
9
+ # Appraisal related files
10
+ /gemfiles/.bundle/
11
+ /gemfiles/*.gemfile.lock
12
+
9
13
  /Gemfile.lock
10
14
  .byebug_history
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "spec/fixtures/mauth-protocol-test-suite"]
2
+ path = spec/fixtures/mauth-protocol-test-suite
3
+ url = https://github.com/mdsol/mauth-protocol-test-suite.git
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.8
6
- - 2.4.6
7
- - 2.5.5
8
- - 2.6.3
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.0.1 && bundle --version
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' https://raw.githubusercontent.com/mdsol/fossa_ci_scripts/master/travis_ci/fossa_install.sh | bash -s -- -b $TRAVIS_BUILD_DIR
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' https://raw.githubusercontent.com/mdsol/fossa_ci_scripts/master/travis_ci/fossa_run.sh | bash -s -- -b $TRAVIS_BUILD_DIR
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: "$TRAVIS_RUBY_VERSION == 2.6.3"
41
+ condition: $TRAVIS_RUBY_VERSION == 2.7 && $BUNDLE_GEMFILE == $TRAVIS_BUILD_DIR/gemfiles/faraday_1.x.gemfile
data/Appraisals ADDED
@@ -0,0 +1,7 @@
1
+ appraise 'faraday_0.x' do
2
+ gem 'faraday', '0.9.0'
3
+ end
4
+
5
+ appraise 'faraday_1.x' do
6
+ gem 'faraday', '~> 1.0'
7
+ end
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
@@ -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
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'faraday', '~> 0.15'
3
+ gem 'faraday', '~> 1.0'
4
4
  gem 'mauth-client', path: '..'
@@ -1,41 +1,47 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- mauth-client (4.1.0)
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 (~> 0.7)
8
- faraday_middleware (~> 0.9)
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
- coderay (1.1.2)
16
- dice_bag (1.3.1)
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 (~> 0.0)
20
- diff-lcs (1.3)
21
- faraday (0.15.3)
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.12.2)
24
- faraday (>= 0.7.4, < 1.0)
25
- multipart-post (2.0.0)
26
- rack (2.0.6)
27
- rake (12.3.1)
28
- term-ansicolor (1.7.0)
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.20.3)
31
- tins (1.20.2)
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.15)
43
+ faraday (~> 1.0)
38
44
  mauth-client!
39
45
 
40
46
  BUNDLED WITH
41
- 1.17.1
47
+ 2.1.4
data/examples/config.yml CHANGED
@@ -6,3 +6,7 @@ mauth:
6
6
  mauth_api_version: v1
7
7
  app_uuid: <APP UUID>
8
8
  private_key_file: ./mauth_key
9
+ v2_only_authenticate: false
10
+ v2_only_sign_requests: false
11
+ disable_fallback_to_v1_on_v2_failure: true
12
+ v1_only_sign_requests: false
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "faraday", "0.9.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "faraday", "~> 1.0"
6
+
7
+ gemspec path: "../"
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 authenticate with only the highest protocol version present
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
- authenticate_v2!(object)
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::Error::ConnectionFailed, ::Faraday::Error::TimeoutError => e
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] || @cache[app_uuid].expired?
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
- # use UNRESERVED instead of UNSAFE (the default) as UNSAFE doesn't include /
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::Error::ConnectionFailed, ::Faraday::Error::TimeoutError => e
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] = ExpirableSecurityToken.new(security_token, Time.now)
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].security_token
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
@@ -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
- # response_body_digest + <LF> +
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].to_s)
64
- attrs_with_overrides[:encoded_query_params] = encode_query_string(attrs_with_overrides[:query_string].to_s)
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 encode_query_string(q_string)
84
- q_string.split('&').sort.map do |part|
85
- k, e, v = part.partition('=')
86
- uri_escape(k) + e + uri_escape(v)
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 ignore other
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 ||= begin
125
- match = if mcc_authentication
126
- mcc_authentication.match(
127
- /\A(#{MAuth::Client::MWSV2_TOKEN}) ([^:]+):([^:]+)#{MAuth::Client::AUTH_HEADER_DELIMITER}\z/
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MAuth
2
- VERSION = '5.0.2'.freeze
4
+ VERSION = '6.2.0'
3
5
  end
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', '~> 0.7'
22
- spec.add_dependency 'faraday_middleware', '~> 0.9'
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: 5.0.2
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: 2019-08-21 00:00:00.000000000 Z
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.7'
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.7'
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
- rubyforge_project:
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.