mauth-client 4.2.1 → 5.0.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.
data/lib/mauth/rack.rb CHANGED
@@ -15,19 +15,26 @@ module MAuth
15
15
  # from this is false, the request is passed to the app with no authentication performed.
16
16
  class RequestAuthenticator < MAuth::Middleware
17
17
  def call(env)
18
- if should_authenticate?(env)
19
- mauth_request = MAuth::Rack::Request.new(env)
20
- begin
21
- if mauth_client.authentic?(mauth_request)
22
- @app.call(env.merge('mauth.app_uuid' => mauth_request.signature_app_uuid, 'mauth.authentic' => true))
23
- else
24
- response_for_inauthentic_request(env)
25
- end
26
- rescue MAuth::UnableToAuthenticateError
27
- response_for_unable_to_authenticate(env)
18
+ mauth_request = MAuth::Rack::Request.new(env)
19
+ env['mauth.protocol_version'] = mauth_request.protocol_version
20
+
21
+ return @app.call(env) unless should_authenticate?(env)
22
+
23
+ if mauth_client.v2_only_authenticate? && mauth_request.protocol_version == 1
24
+ return response_for_missing_v2(env)
25
+ end
26
+
27
+ begin
28
+ if mauth_client.authentic?(mauth_request)
29
+ @app.call(env.merge!(
30
+ 'mauth.app_uuid' => mauth_request.signature_app_uuid,
31
+ 'mauth.authentic' => true
32
+ ))
33
+ else
34
+ response_for_inauthentic_request(env)
28
35
  end
29
- else
30
- @app.call(env)
36
+ rescue MAuth::UnableToAuthenticateError
37
+ response_for_unable_to_authenticate(env)
31
38
  end
32
39
  end
33
40
 
@@ -61,6 +68,18 @@ module MAuth
61
68
  [500, { 'Content-Type' => 'application/json' }, [JSON.pretty_generate(body)]]
62
69
  end
63
70
  end
71
+
72
+ # response when the requests includes V1 headers but does not include V2
73
+ # headers and the V2_ONLY_AUTHENTICATE flag is set.
74
+ def response_for_missing_v2(env)
75
+ handle_head(env) do
76
+ body = {
77
+ 'type' => 'errors:mauth:missing_v2',
78
+ 'title' => 'This service requires mAuth v2 mcc-authentication header. Upgrade your mAuth library and configure it properly.'
79
+ }
80
+ [401, { 'Content-Type' => 'application/json' }, [JSON.pretty_generate(body)]]
81
+ end
82
+ end
64
83
  end
65
84
 
66
85
  # same as MAuth::Rack::RequestAuthenticator, but does not authenticate /app_status
@@ -70,12 +89,24 @@ module MAuth
70
89
  end
71
90
  end
72
91
 
73
- # signs outgoing responses
92
+ # signs outgoing responses with only the protocol used to sign the request.
74
93
  class ResponseSigner < MAuth::Middleware
75
94
  def call(env)
76
95
  unsigned_response = @app.call(env)
77
- signed_response = mauth_client.signed(MAuth::Rack::Response.new(*unsigned_response))
78
- signed_response.status_headers_body
96
+
97
+ method =
98
+ if env['mauth.protocol_version'] == 2
99
+ :signed_v2
100
+ elsif env['mauth.protocol_version'] == 1
101
+ :signed_v1
102
+ else
103
+ # if no protocol was supplied then use `signed` which either signs
104
+ # with both protocol versions (by default) or only v2 when the
105
+ # v2_only_sign_requests flag is set to true.
106
+ :signed
107
+ end
108
+ response = mauth_client.send(method, MAuth::Rack::Response.new(*unsigned_response))
109
+ response.status_headers_body
79
110
  end
80
111
  end
81
112
 
@@ -93,7 +124,12 @@ module MAuth
93
124
  env['rack.input'].rewind
94
125
  body = env['rack.input'].read
95
126
  env['rack.input'].rewind
96
- { verb: env['REQUEST_METHOD'], request_url: env['PATH_INFO'], body: body }
127
+ {
128
+ verb: env['REQUEST_METHOD'],
129
+ request_url: env['PATH_INFO'],
130
+ body: body,
131
+ query_string: env['QUERY_STRING']
132
+ }
97
133
  end
98
134
  end
99
135
 
@@ -104,6 +140,14 @@ module MAuth
104
140
  def x_mws_authentication
105
141
  @env['HTTP_X_MWS_AUTHENTICATION']
106
142
  end
143
+
144
+ def mcc_time
145
+ @env['HTTP_MCC_TIME']
146
+ end
147
+
148
+ def mcc_authentication
149
+ @env['HTTP_MCC_AUTHENTICATION']
150
+ end
107
151
  end
108
152
 
109
153
  # representation of a response composed from a rack response (status, headers, body) which
@@ -4,7 +4,7 @@ module MAuth
4
4
  # module which composes a string to sign.
5
5
  #
6
6
  # includer must provide
7
- # - SIGNATURE_COMPONENTS constant - array of keys to get from #attributes_for_signing
7
+ # - SIGNATURE_COMPONENTS OR SIGNATURE_COMPONENTS_V2 constant - array of keys to get from
8
8
  # - #attributes_for_signing
9
9
  # - #merge_headers (takes a Hash of headers; returns an instance of includer's own class whose
10
10
  # headers have been updated with the argument headers)
@@ -12,15 +12,89 @@ module MAuth
12
12
  # composes a string suitable for private-key signing from the SIGNATURE_COMPONENTS keys of
13
13
  # attributes for signing, which are themselves taken from #attributes_for_signing and
14
14
  # the given argument more_attributes
15
- def string_to_sign(more_attributes)
15
+
16
+ # the string to sign for V1 protocol will be (where LF is line feed character)
17
+ # for requests:
18
+ # string_to_sign =
19
+ # http_verb + <LF> +
20
+ # resource_url_path (no host, port or query string; first "/" is included) + <LF> +
21
+ # request_body + <LF> +
22
+ # app_uuid + <LF> +
23
+ # current_seconds_since_epoch
24
+ #
25
+ # for responses:
26
+ # string_to_sign =
27
+ # status_code_string + <LF> +
28
+ # response_body_digest + <LF> +
29
+ # app_uuid + <LF> +
30
+ # current_seconds_since_epoch
31
+ def string_to_sign_v1(more_attributes)
16
32
  attributes_for_signing = self.attributes_for_signing.merge(more_attributes)
17
33
  missing_attributes = self.class::SIGNATURE_COMPONENTS.select { |key| !attributes_for_signing.key?(key) || attributes_for_signing[key].nil? }
18
34
  missing_attributes.delete(:body) # body may be omitted
19
35
  if missing_attributes.any?
20
36
  raise(UnableToSignError, "Missing required attributes to sign: #{missing_attributes.inspect}\non object to sign: #{inspect}")
21
37
  end
22
- string = self.class::SIGNATURE_COMPONENTS.map { |k| attributes_for_signing[k].to_s }.join("\n")
23
- Digest::SHA512.hexdigest(string)
38
+ self.class::SIGNATURE_COMPONENTS.map { |k| attributes_for_signing[k].to_s }.join("\n")
39
+ end
40
+
41
+ # the string to sign for V2 protocol will be (where LF is line feed character)
42
+ # for requests:
43
+ # string_to_sign =
44
+ # http_verb + <LF> +
45
+ # resource_url_path (no host, port or query string; first "/" is included) + <LF> +
46
+ # request_body_digest + <LF> +
47
+ # app_uuid + <LF> +
48
+ # current_seconds_since_epoch + <LF> +
49
+ # encoded_query_params
50
+ #
51
+ # for responses:
52
+ # string_to_sign =
53
+ # status_code_string + <LF> +
54
+ # response_body_digest + <LF> +
55
+ # app_uuid + <LF> +
56
+ # current_seconds_since_epoch
57
+ def string_to_sign_v2(override_attrs)
58
+ attrs_with_overrides = self.attributes_for_signing.merge(override_attrs)
59
+
60
+ # memoization of body_digest to avoid hashing three times when we call
61
+ # 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)
65
+
66
+ missing_attributes = self.class::SIGNATURE_COMPONENTS_V2.reject do |key|
67
+ attrs_with_overrides.dig(key)
68
+ end
69
+
70
+ missing_attributes.delete(:body_digest) # body may be omitted
71
+ missing_attributes.delete(:encoded_query_params) # query_string may be omitted
72
+ if missing_attributes.any?
73
+ raise(UnableToSignError, "Missing required attributes to sign: #{missing_attributes.inspect}\non object to sign: #{inspect}")
74
+ end
75
+
76
+ self.class::SIGNATURE_COMPONENTS_V2.map do |k|
77
+ attrs_with_overrides[k].to_s.force_encoding('UTF-8')
78
+ end.join("\n")
79
+ end
80
+
81
+ # sorts query string parameters by codepoint, uri encodes keys and values,
82
+ # 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)
87
+ end.join('&')
88
+ end
89
+
90
+ # percent encodes special characters, preserving character encoding.
91
+ # encodes space as '%20'
92
+ # does not encode A-Z, a-z, 0-9, hyphen ( - ), underscore ( _ ), period ( . ),
93
+ # or tilde ( ~ )
94
+ # NOTE the CGI.escape spec changed in 2.5 to not escape tildes. we gsub
95
+ # tilde encoding back to tildes to account for older Rubies
96
+ def uri_escape(string)
97
+ CGI.escape(string).gsub(/\+|%7E/, '+' => '%20', '%7E' => '~')
24
98
  end
25
99
 
26
100
  def initialize(attributes_for_signing)
@@ -35,13 +109,27 @@ module MAuth
35
109
  # methods for an incoming object which is expected to have a signature.
36
110
  #
37
111
  # includer must provide
112
+ # - #mcc_authentication which returns that header's value
113
+ # - #mcc_time
114
+ # OR
38
115
  # - #x_mws_authentication which returns that header's value
39
116
  # - #x_mws_time
40
117
  module Signed
41
- # returns a hash with keys :token, :app_uuid, and :signature parsed from the X-MWS-Authentication header
118
+ # mauth_client will authenticate with the highest protocol version present and ignore other
119
+ # protocol versions.
120
+ # returns a hash with keys :token, :app_uuid, and :signature parsed from the MCC-Authentication header
121
+ # if it is present and if not then the X-MWS-Authentication header if it is present.
122
+ # Note MWSV2 protocol no longer allows more than one space between the token and app uuid.
42
123
  def signature_info
43
124
  @signature_info ||= begin
44
- match = x_mws_authentication && x_mws_authentication.match(/\A([^ ]+) *([^:]+):([^:]+)\z/)
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
+
45
133
  match ? { token: match[1], app_uuid: match[2], signature: match[3] } : {}
46
134
  end
47
135
  end
@@ -57,17 +145,36 @@ module MAuth
57
145
  def signature
58
146
  signature_info[:signature]
59
147
  end
148
+
149
+ def protocol_version
150
+ if !mcc_authentication.to_s.strip.empty?
151
+ 2
152
+ elsif !x_mws_authentication.to_s.strip.empty?
153
+ 1
154
+ end
155
+ end
60
156
  end
61
157
 
62
158
  # virtual base class for signable requests
63
159
  class Request
64
- SIGNATURE_COMPONENTS = [:verb, :request_url, :body, :app_uuid, :time].freeze
160
+ SIGNATURE_COMPONENTS = %i[verb request_url body app_uuid time].freeze
161
+ SIGNATURE_COMPONENTS_V2 =
162
+ %i[
163
+ verb
164
+ request_url
165
+ body_digest
166
+ app_uuid
167
+ time
168
+ encoded_query_params
169
+ ].freeze
170
+
65
171
  include Signable
66
172
  end
67
173
 
68
174
  # virtual base class for signable responses
69
175
  class Response
70
- SIGNATURE_COMPONENTS = [:status_code, :body, :app_uuid, :time].freeze
176
+ SIGNATURE_COMPONENTS = %i[status_code body app_uuid time].freeze
177
+ SIGNATURE_COMPONENTS_V2 = %i[status_code body_digest app_uuid time].freeze
71
178
  include Signable
72
179
  end
73
180
  end
data/lib/mauth/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module MAuth
2
- VERSION = '4.2.1'.freeze
2
+ VERSION = '5.0.0'.freeze
3
3
  end
data/mauth-client.gemspec CHANGED
@@ -18,9 +18,8 @@ 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.17', '< 1.0'
22
- spec.add_dependency 'faraday_middleware', '>= 0.9', '< 2.0'
23
- spec.add_dependency 'faraday-http-cache', '>= 2.0', '< 3.0'
21
+ spec.add_dependency 'faraday', '~> 0.7'
22
+ spec.add_dependency 'faraday_middleware', '~> 0.9'
24
23
  spec.add_dependency 'term-ansicolor', '~> 1.0'
25
24
  spec.add_dependency 'coderay', '~> 1.0'
26
25
  spec.add_dependency 'rack'
@@ -33,4 +32,5 @@ Gem::Specification.new do |spec|
33
32
  spec.add_development_dependency 'rspec', '~> 3.8'
34
33
  spec.add_development_dependency 'simplecov', '~> 0.16'
35
34
  spec.add_development_dependency 'timecop', '~> 0.9'
35
+ spec.add_development_dependency 'benchmark-ips', '~> 2.7'
36
36
  end
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.2.1
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Szenher
@@ -11,68 +11,36 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2021-09-27 00:00:00.000000000 Z
14
+ date: 2019-07-23 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
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: '0.17'
23
- - - "<"
20
+ - - "~>"
24
21
  - !ruby/object:Gem::Version
25
- version: '1.0'
22
+ version: '0.7'
26
23
  type: :runtime
27
24
  prerelease: false
28
25
  version_requirements: !ruby/object:Gem::Requirement
29
26
  requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: '0.17'
33
- - - "<"
27
+ - - "~>"
34
28
  - !ruby/object:Gem::Version
35
- version: '1.0'
29
+ version: '0.7'
36
30
  - !ruby/object:Gem::Dependency
37
31
  name: faraday_middleware
38
32
  requirement: !ruby/object:Gem::Requirement
39
33
  requirements:
40
- - - ">="
34
+ - - "~>"
41
35
  - !ruby/object:Gem::Version
42
36
  version: '0.9'
43
- - - "<"
44
- - !ruby/object:Gem::Version
45
- version: '2.0'
46
37
  type: :runtime
47
38
  prerelease: false
48
39
  version_requirements: !ruby/object:Gem::Requirement
49
40
  requirements:
50
- - - ">="
41
+ - - "~>"
51
42
  - !ruby/object:Gem::Version
52
43
  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
44
  - !ruby/object:Gem::Dependency
77
45
  name: term-ansicolor
78
46
  requirement: !ruby/object:Gem::Requirement
@@ -233,6 +201,20 @@ dependencies:
233
201
  - - "~>"
234
202
  - !ruby/object:Gem::Version
235
203
  version: '0.9'
204
+ - !ruby/object:Gem::Dependency
205
+ name: benchmark-ips
206
+ requirement: !ruby/object:Gem::Requirement
207
+ requirements:
208
+ - - "~>"
209
+ - !ruby/object:Gem::Version
210
+ version: '2.7'
211
+ type: :development
212
+ prerelease: false
213
+ version_requirements: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - "~>"
216
+ - !ruby/object:Gem::Version
217
+ version: '2.7'
236
218
  description: Client for signing and authentication of requests and responses with
237
219
  mAuth authentication. Includes middleware for Rack and Faraday for incoming and
238
220
  outgoing requests and responses.
@@ -269,11 +251,17 @@ files:
269
251
  - lib/mauth-client.rb
270
252
  - lib/mauth/autoload.rb
271
253
  - lib/mauth/client.rb
254
+ - lib/mauth/client/authenticator_base.rb
255
+ - lib/mauth/client/local_authenticator.rb
256
+ - lib/mauth/client/remote_authenticator.rb
257
+ - lib/mauth/client/security_token_cacher.rb
258
+ - lib/mauth/client/signer.rb
272
259
  - lib/mauth/core_ext.rb
273
260
  - lib/mauth/dice_bag/mauth.rb.dice
274
261
  - lib/mauth/dice_bag/mauth.yml.dice
275
262
  - lib/mauth/dice_bag/mauth_key.dice
276
263
  - lib/mauth/dice_bag/mauth_templates.rb
264
+ - lib/mauth/errors.rb
277
265
  - lib/mauth/fake/rack.rb
278
266
  - lib/mauth/faraday.rb
279
267
  - lib/mauth/middleware.rb
@@ -302,7 +290,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
302
290
  - !ruby/object:Gem::Version
303
291
  version: '0'
304
292
  requirements: []
305
- rubygems_version: 3.0.8
293
+ rubygems_version: 3.0.4
306
294
  signing_key:
307
295
  specification_version: 4
308
296
  summary: Sign and authenticate requests and responses with mAuth authentication.