mauth-client 5.0.2 → 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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.