mauth-client 4.2.1 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mauth/client.rb CHANGED
@@ -7,139 +7,98 @@ require 'mauth/core_ext'
7
7
  require 'mauth/autoload'
8
8
  require 'mauth/dice_bag/mauth_templates'
9
9
  require 'mauth/version'
10
- require 'faraday-http-cache'
11
- require 'mauth/faraday'
10
+ require 'mauth/client/authenticator_base'
11
+ require 'mauth/client/local_authenticator'
12
+ require 'mauth/client/remote_authenticator'
13
+ require 'mauth/client/signer'
14
+ require 'mauth/errors'
12
15
 
13
16
  module MAuth
17
+ # does operations which require a private key and corresponding app uuid. this is primarily:
18
+ # - signing outgoing requests and responses
19
+ # - authenticating incoming requests and responses, which may require retrieving the appropriate
20
+ # public key from mAuth (which requires a request to mAuth which is signed using the private
21
+ # key)
22
+ #
23
+ # this nominally operates on request and response objects, but really the only requirements are
24
+ # that the object responds to the methods of MAuth::Signable and/or MAuth::Signed (as
25
+ # appropriate)
14
26
  class Client
15
- class << self
16
- # returns a configuration (to be passed to MAuth::Client.new) which is configured from information stored in
17
- # standard places. all of which is overridable by options in case some defaults do not apply.
18
- #
19
- # options (may be symbols or strings) - any or all may be omitted where your usage conforms to the defaults.
20
- # - root: the path relative to which this method looks for configuration yaml files. defaults to Rails.root
21
- # if ::Rails is defined, otherwise ENV['RAILS_ROOT'], ENV['RACK_ROOT'], ENV['APP_ROOT'], or '.'
22
- # - environment: the environment, pertaining to top-level keys of the configuration yaml files. by default,
23
- # tries Rails.environment, ENV['RAILS_ENV'], and ENV['RACK_ENV'], and falls back to 'development' if none
24
- # of these are set.
25
- # - mauth_config - MAuth configuration. defaults to load this from a yaml file (see mauth_config_yml option)
26
- # which is assumed to be keyed with the environment at the root. if this is specified, no yaml file is
27
- # loaded, and the given config is passed through with any other defaults applied. at the moment, the only
28
- # other default is to set the logger.
29
- # - mauth_config_yml - specifies where a mauth configuration yaml file can be found. by default checks
30
- # ENV['MAUTH_CONFIG_YML'] or a file 'config/mauth.yml' relative to the root.
31
- # - logger - by default checks ::Rails.logger
32
- def default_config(options = {})
33
- options = options.stringify_symbol_keys
27
+ MWS_TOKEN = 'MWS'.freeze
28
+ MWSV2_TOKEN = 'MWSV2'.freeze
29
+ AUTH_HEADER_DELIMITER = ';'.freeze
34
30
 
35
- # find the app_root (relative to which we look for yaml files). note that this
36
- # is different than MAuth::Client.root, the root of the mauth-client library.
37
- app_root = options['root'] || begin
38
- if Object.const_defined?('Rails') && ::Rails.respond_to?(:root) && ::Rails.root
39
- Rails.root
40
- else
41
- ENV['RAILS_ROOT'] || ENV['RACK_ROOT'] || ENV['APP_ROOT'] || '.'
42
- end
43
- end
31
+ include AuthenticatorBase
32
+ include Signer
44
33
 
45
- # find the environment (with which yaml files are keyed)
46
- env = options['environment'] || begin
47
- if Object.const_defined?('Rails') && ::Rails.respond_to?(:environment)
48
- Rails.environment
49
- else
50
- ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
51
- end
34
+ # returns a configuration (to be passed to MAuth::Client.new) which is configured from information stored in
35
+ # standard places. all of which is overridable by options in case some defaults do not apply.
36
+ #
37
+ # options (may be symbols or strings) - any or all may be omitted where your usage conforms to the defaults.
38
+ # - root: the path relative to which this method looks for configuration yaml files. defaults to Rails.root
39
+ # if ::Rails is defined, otherwise ENV['RAILS_ROOT'], ENV['RACK_ROOT'], ENV['APP_ROOT'], or '.'
40
+ # - environment: the environment, pertaining to top-level keys of the configuration yaml files. by default,
41
+ # tries Rails.environment, ENV['RAILS_ENV'], and ENV['RACK_ENV'], and falls back to 'development' if none
42
+ # of these are set.
43
+ # - mauth_config - MAuth configuration. defaults to load this from a yaml file (see mauth_config_yml option)
44
+ # which is assumed to be keyed with the environment at the root. if this is specified, no yaml file is
45
+ # loaded, and the given config is passed through with any other defaults applied. at the moment, the only
46
+ # other default is to set the logger.
47
+ # - mauth_config_yml - specifies where a mauth configuration yaml file can be found. by default checks
48
+ # ENV['MAUTH_CONFIG_YML'] or a file 'config/mauth.yml' relative to the root.
49
+ # - logger - by default checks ::Rails.logger
50
+ def self.default_config(options = {})
51
+ options = options.stringify_symbol_keys
52
+
53
+ # find the app_root (relative to which we look for yaml files). note that this
54
+ # is different than MAuth::Client.root, the root of the mauth-client library.
55
+ app_root = options['root'] || begin
56
+ if Object.const_defined?('Rails') && ::Rails.respond_to?(:root) && ::Rails.root
57
+ Rails.root
58
+ else
59
+ ENV['RAILS_ROOT'] || ENV['RACK_ROOT'] || ENV['APP_ROOT'] || '.'
52
60
  end
61
+ end
53
62
 
54
- # find mauth config, given on options, or in a file at
55
- # ENV['MAUTH_CONFIG_YML'] or config/mauth.yml in the app_root
56
- mauth_config = options['mauth_config'] || begin
57
- mauth_config_yml = options['mauth_config_yml']
58
- mauth_config_yml ||= ENV['MAUTH_CONFIG_YML']
59
- default_loc = 'config/mauth.yml'
60
- default_yml = File.join(app_root, default_loc)
61
- mauth_config_yml ||= default_yml if File.exist?(default_yml)
62
- if mauth_config_yml && File.exist?(mauth_config_yml)
63
- whole_config = ConfigFile.load(mauth_config_yml)
64
- errmessage = "#{mauth_config_yml} config has no key #{env} - it has keys #{whole_config.keys.inspect}"
65
- whole_config[env] || raise(MAuth::Client::ConfigurationError, errmessage)
66
- else
67
- raise MAuth::Client::ConfigurationError, "could not find mauth config yaml file. this file may be " \
68
- "placed in #{default_loc}, specified with the mauth_config_yml option, or specified with the " \
69
- "MAUTH_CONFIG_YML environment variable."
70
- end
63
+ # find the environment (with which yaml files are keyed)
64
+ env = options['environment'] || begin
65
+ if Object.const_defined?('Rails') && ::Rails.respond_to?(:environment)
66
+ Rails.environment
67
+ else
68
+ ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
69
+ end
70
+ end
71
+
72
+ # find mauth config, given on options, or in a file at
73
+ # ENV['MAUTH_CONFIG_YML'] or config/mauth.yml in the app_root
74
+ mauth_config = options['mauth_config'] || begin
75
+ mauth_config_yml = options['mauth_config_yml']
76
+ mauth_config_yml ||= ENV['MAUTH_CONFIG_YML']
77
+ default_loc = 'config/mauth.yml'
78
+ default_yml = File.join(app_root, default_loc)
79
+ mauth_config_yml ||= default_yml if File.exist?(default_yml)
80
+ if mauth_config_yml && File.exist?(mauth_config_yml)
81
+ whole_config = ConfigFile.load(mauth_config_yml)
82
+ errmessage = "#{mauth_config_yml} config has no key #{env} - it has keys #{whole_config.keys.inspect}"
83
+ whole_config[env] || raise(MAuth::Client::ConfigurationError, errmessage)
84
+ else
85
+ raise MAuth::Client::ConfigurationError, "could not find mauth config yaml file. this file may be " \
86
+ "placed in #{default_loc}, specified with the mauth_config_yml option, or specified with the " \
87
+ "MAUTH_CONFIG_YML environment variable."
71
88
  end
89
+ end
72
90
 
73
- unless mauth_config.key?('logger')
74
- # the logger. Rails.logger if it exists, otherwise, no logger
75
- mauth_config['logger'] = options['logger'] || begin
76
- if Object.const_defined?('Rails') && ::Rails.respond_to?(:logger)
77
- Rails.logger
78
- end
91
+ unless mauth_config.key?('logger')
92
+ # the logger. Rails.logger if it exists, otherwise, no logger
93
+ mauth_config['logger'] = options['logger'] || begin
94
+ if Object.const_defined?('Rails') && ::Rails.respond_to?(:logger)
95
+ Rails.logger
79
96
  end
80
97
  end
81
-
82
- mauth_config
83
98
  end
84
- end
85
- end
86
99
 
87
- class ConfigFile
88
- GITHUB_URL = 'https://github.com/mdsol/mauth-client-ruby'.freeze
89
- @config = {}
90
-
91
- def self.load(path)
92
- unless File.exist?(path)
93
- raise "File #{path} not found. Please visit #{GITHUB_URL} for details."
94
- end
95
-
96
- @config[path] ||= YAML.load_file(path)
97
- unless @config[path]
98
- raise "File #{path} does not contain proper YAML information. Visit #{GITHUB_URL} for details."
99
- end
100
- @config[path]
100
+ mauth_config
101
101
  end
102
- end
103
- end
104
-
105
- module MAuth
106
- # mAuth client was unable to verify the authenticity of a signed object (this does NOT mean the
107
- # object is inauthentic). typically due to a failure communicating with the mAuth service, in
108
- # which case the error may include the attribute mauth_service_response - a response from
109
- # the mauth service (if it was contactable at all), which may contain more information about
110
- # the error.
111
- class UnableToAuthenticateError < StandardError
112
- # the response from the MAuth service encountered when attempting to retrieve authentication
113
- attr_accessor :mauth_service_response
114
- end
115
-
116
- # used to indicate that an object was expected to be validly signed but its signature does not
117
- # match its contents, and so is inauthentic.
118
- class InauthenticError < StandardError
119
- end
120
-
121
- # Used when the incoming request does not contain any mAuth related information
122
- class MauthNotPresent < StandardError
123
- end
124
-
125
-
126
- # required information for signing was missing
127
- class UnableToSignError < StandardError
128
- end
129
-
130
- # does operations which require a private key and corresponding app uuid. this is primarily:
131
- # - signing outgoing requests and responses
132
- # - authenticating incoming requests and responses, which may require retrieving the appropriate
133
- # public key from mAuth (which requires a request to mAuth which is signed using the private
134
- # key)
135
- #
136
- # this nominally operates on request and response objects, but really the only requirements are
137
- # that the object responds to the methods of MAuth::Signable and/or MAuth::Signed (as
138
- # appropriate)
139
- class Client
140
- class ConfigurationError < StandardError; end
141
-
142
- MWS_TOKEN = 'MWS'.freeze
143
102
 
144
103
  # new client with the given App UUID and public key. config may include the following (all
145
104
  # config keys may be strings or symbols):
@@ -192,6 +151,8 @@ module MAuth
192
151
  request_config.merge!(symbolize_keys(given_config['faraday_options'])) if given_config['faraday_options']
193
152
  @config['faraday_options'] = { request: request_config } || {}
194
153
  @config['ssl_certs_path'] = given_config['ssl_certs_path'] if given_config['ssl_certs_path']
154
+ @config['v2_only_authenticate'] = given_config['v2_only_authenticate'].to_s.downcase == 'true'
155
+ @config['v2_only_sign_requests'] = given_config['v2_only_sign_requests'].to_s.downcase == 'true'
195
156
 
196
157
  # if 'authenticator' was given, don't override that - including if it was given as nil / false
197
158
  if given_config.key?('authenticator')
@@ -236,6 +197,14 @@ module MAuth
236
197
  @config['ssl_certs_path']
237
198
  end
238
199
 
200
+ def v2_only_sign_requests?
201
+ @config['v2_only_sign_requests']
202
+ end
203
+
204
+ def v2_only_authenticate?
205
+ @config['v2_only_authenticate']
206
+ end
207
+
239
208
  def assert_private_key(err)
240
209
  raise err unless private_key
241
210
  end
@@ -257,259 +226,23 @@ module MAuth
257
226
  end
258
227
  hash
259
228
  end
229
+ end
260
230
 
261
- # methods to sign requests and responses. part of MAuth::Client
262
- module Signer
263
- # takes an outgoing request or response object, and returns an object of the same class
264
- # whose headers are updated to include mauth's signature headers
265
- def signed(object, attributes = {})
266
- object.merge_headers(signed_headers(object, attributes))
267
- end
268
-
269
- # takes a signable object (outgoing request or response). returns a hash of headers to be
270
- # applied tothe object which comprise its signature.
271
- def signed_headers(object, attributes = {})
272
- attributes = { time: Time.now.to_i.to_s, app_uuid: client_app_uuid }.merge(attributes)
273
- signature = self.signature(object, attributes)
274
- { 'X-MWS-Authentication' => "#{MWS_TOKEN} #{client_app_uuid}:#{signature}", 'X-MWS-Time' => attributes[:time] }
275
- end
276
-
277
- # takes a signable object (outgoing request or response). returns a mauth signature string
278
- # for that object.
279
- def signature(object, attributes = {})
280
- assert_private_key(UnableToSignError.new("mAuth client cannot sign without a private key!"))
281
- attributes = { time: Time.now.to_i.to_s, app_uuid: client_app_uuid }.merge(attributes)
282
- signature = Base64.encode64(private_key.private_encrypt(object.string_to_sign(attributes))).delete("\n")
283
- end
284
- end
285
- include Signer
286
-
287
- # methods common to RemoteRequestAuthenticator and LocalAuthenticator
288
- module Authenticator
289
- ALLOWED_DRIFT_SECONDS = 300
290
-
291
- # takes an incoming request or response object, and returns whether
292
- # the object is authentic according to its signature.
293
- def authentic?(object)
294
- log_authentication_request(object)
295
- begin
296
- authenticate!(object)
297
- true
298
- rescue InauthenticError, MauthNotPresent
299
- false
300
- end
301
- end
302
-
303
- # raises InauthenticError unless the given object is authentic
304
- def authenticate!(object)
305
- authentication_present!(object)
306
- time_valid!(object)
307
- token_valid!(object)
308
- signature_valid!(object)
309
- rescue MauthNotPresent => e
310
- logger.warn "mAuth signature not present on #{object.class}. Exception: #{e.message}"
311
- raise
312
- rescue InauthenticError => e
313
- logger.error "mAuth signature authentication failed for #{object.class}. Exception: #{e.message}"
314
- raise
315
- rescue UnableToAuthenticateError => e
316
- logger.error "Unable to authenticate with MAuth for #{object.class}. Exception: #{e.message}"
317
- raise
318
- end
319
-
320
- private
321
-
322
- # Note: This log is likely consumed downstream and the contents SHOULD NOT be changed without a thorough review of downstream consumers.
323
- def log_authentication_request(object)
324
- object_app_uuid = object.signature_app_uuid || '[none provided]'
325
- logger.info "Mauth-client attempting to authenticate request from app with mauth app uuid #{object_app_uuid} to app with mauth app uuid #{client_app_uuid}."
326
- end
327
-
328
- def authentication_present!(object)
329
- if object.x_mws_authentication.nil? || object.x_mws_authentication !~ /\S/
330
- raise MauthNotPresent, "Authentication Failed. No mAuth signature present; X-MWS-Authentication header is blank."
331
- end
332
- end
333
-
334
- def time_valid!(object, now = Time.now)
335
- if object.x_mws_time.nil?
336
- raise InauthenticError, "Time verification failed. No x-mws-time present."
337
- elsif !(-ALLOWED_DRIFT_SECONDS..ALLOWED_DRIFT_SECONDS).cover?(now.to_i - object.x_mws_time.to_i)
338
- raise InauthenticError, "Time verification failed. #{object.x_mws_time} not within #{ALLOWED_DRIFT_SECONDS} of #{now}"
339
- end
340
- end
341
-
342
- def token_valid!(object)
343
- unless object.signature_token == MWS_TOKEN
344
- raise InauthenticError, "Token verification failed. Expected #{MWS_TOKEN.inspect}; token was #{object.signature_token}"
345
- end
346
- end
347
- end
348
- include Authenticator
349
-
350
- # methods to verify the authenticity of signed requests and responses locally, retrieving
351
- # public keys from the mAuth service as needed
352
- module LocalAuthenticator
353
- private
354
-
355
- def signature_valid!(object)
356
- # We are in an unfortunate situation in which Euresource is percent-encoding parts of paths, but not
357
- # all of them. In particular, Euresource is percent-encoding all special characters save for '/'.
358
- # Also, unfortunately, Nginx unencodes URIs before sending them off to served applications, though
359
- # other web servers (particularly those we typically use for local testing) do not. The various forms
360
- # of the expected string to sign are meant to cover the main cases.
361
- # TODO: Revisit and simplify this unfortunate situation.
362
-
363
- original_request_uri = object.attributes_for_signing[:request_url]
364
-
365
- # craft an expected string-to-sign without doing any percent-encoding
366
- expected_no_reencoding = object.string_to_sign(time: object.x_mws_time, app_uuid: object.signature_app_uuid)
367
-
368
- # do a simple percent reencoding variant of the path
369
- object.attributes_for_signing[:request_url] = CGI.escape(original_request_uri.to_s)
370
- expected_for_percent_reencoding = object.string_to_sign(time: object.x_mws_time, app_uuid: object.signature_app_uuid)
371
-
372
- # do a moderately complex Euresource-style reencoding of the path
373
- object.attributes_for_signing[:request_url] = euresource_escape(original_request_uri.to_s)
374
- expected_euresource_style_reencoding = object.string_to_sign(time: object.x_mws_time, app_uuid: object.signature_app_uuid)
375
-
376
- # reset the object original request_uri, just in case we need it again
377
- object.attributes_for_signing[:request_url] = original_request_uri
378
-
379
- pubkey = OpenSSL::PKey::RSA.new(retrieve_public_key(object.signature_app_uuid))
380
- begin
381
- actual = pubkey.public_decrypt(Base64.decode64(object.signature))
382
- rescue OpenSSL::PKey::PKeyError
383
- raise InauthenticError, "Public key decryption of signature failed!\n#{$!.class}: #{$!.message}"
384
- end
385
- # TODO: time-invariant comparison instead of #== ?
386
- unless expected_no_reencoding == actual || expected_euresource_style_reencoding == actual || expected_for_percent_reencoding == actual
387
- raise InauthenticError, "Signature verification failed for #{object.class}"
388
- end
389
- end
390
-
391
- # Note: RFC 3986 (https://www.ietf.org/rfc/rfc3986.txt) reserves the forward slash "/"
392
- # and number sign "#" as component delimiters. Since these are valid URI components,
393
- # they are decoded back into characters here to avoid signature invalidation
394
- def euresource_escape(str)
395
- CGI.escape(str).gsub(/%2F|%23/, "%2F" => "/", "%23" => "#")
396
- end
397
-
398
- def retrieve_public_key(app_uuid)
399
- retrieve_security_token(app_uuid)['security_token']['public_key_str']
400
- end
401
-
402
- def retrieve_security_token(app_uuid)
403
- security_token_cacher.get(app_uuid)
404
- end
405
-
406
- def security_token_cacher
407
- @security_token_cacher ||= SecurityTokenCacher.new(self)
408
- end
409
- class SecurityTokenCacher
410
-
411
- def initialize(mauth_client)
412
- @mauth_client = mauth_client
413
- # TODO: should this be UnableToSignError?
414
- @mauth_client.assert_private_key(
415
- UnableToAuthenticateError.new("Cannot fetch public keys from mAuth service without a private key!")
416
- )
417
- end
418
-
419
- def get(app_uuid)
420
- # url-encode the app_uuid to prevent trickery like escaping upward with ../../ in a malicious
421
- # app_uuid - probably not exploitable, but this is the right way to do it anyway.
422
- # use UNRESERVED instead of UNSAFE (the default) as UNSAFE doesn't include /
423
- url_encoded_app_uuid = URI.escape(app_uuid, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
424
- path = "/mauth/#{@mauth_client.mauth_api_version}/security_tokens/#{url_encoded_app_uuid}.json"
425
- response = signed_mauth_connection.get(path)
426
-
427
- case response.status
428
- when 200
429
- security_token_from(response.body)
430
- when 404
431
- # signing with a key mAuth doesn't know about is considered inauthentic
432
- raise InauthenticError, "mAuth service responded with 404 looking up public key for #{app_uuid}"
433
- else
434
- @mauth_client.send(:mauth_service_response_error, response)
435
- end
436
- rescue ::Faraday::ConnectionFailed, ::Faraday::TimeoutError => e
437
- msg = "mAuth service did not respond; received #{e.class}: #{e.message}"
438
- @mauth_client.logger.error("Unable to authenticate with MAuth. Exception #{msg}")
439
- raise UnableToAuthenticateError, msg
440
- end
441
-
442
- private
443
-
444
- def security_token_from(response_body)
445
- JSON.parse response_body
446
- rescue JSON::ParserError => e
447
- msg = "mAuth service responded with unparseable json: #{response_body}\n#{e.class}: #{e.message}"
448
- @mauth_client.logger.error("Unable to authenticate with MAuth. Exception #{msg}")
449
- raise UnableToAuthenticateError, msg
450
- end
451
-
452
- def signed_mauth_connection
453
- @signed_mauth_connection ||= begin
454
- if @mauth_client.ssl_certs_path
455
- @mauth_client.faraday_options[:ssl] = { ca_path: @mauth_client.ssl_certs_path }
456
- end
231
+ module ConfigFile
232
+ GITHUB_URL = 'https://github.com/mdsol/mauth-client-ruby'.freeze
233
+ @config = {}
457
234
 
458
- ::Faraday.new(@mauth_client.mauth_baseurl, @mauth_client.faraday_options) do |builder|
459
- builder.use MAuth::Faraday::MAuthClientUserAgent
460
- builder.use MAuth::Faraday::RequestSigner, 'mauth_client' => @mauth_client
461
- builder.use :http_cache, logger: MAuth::Client.new.logger, shared_cache: false
462
- builder.adapter ::Faraday.default_adapter
463
- end
464
- end
465
- end
235
+ def self.load(path)
236
+ unless File.exist?(path)
237
+ raise "File #{path} not found. Please visit #{GITHUB_URL} for details."
466
238
  end
467
- end
468
239
 
469
- # methods for remotely authenticating a request by sending it to the mauth service
470
- module RemoteRequestAuthenticator
471
- private
472
-
473
- # takes an incoming request object (no support for responses currently), and errors if the
474
- # object is not authentic according to its signature
475
- def signature_valid!(object)
476
- raise ArgumentError, "Remote Authenticator can only authenticate requests; received #{object.inspect}" unless object.is_a?(MAuth::Request)
477
- authentication_ticket = {
478
- 'verb' => object.attributes_for_signing[:verb],
479
- 'app_uuid' => object.signature_app_uuid,
480
- 'client_signature' => object.signature,
481
- 'request_url' => object.attributes_for_signing[:request_url],
482
- 'request_time' => object.x_mws_time,
483
- 'b64encoded_body' => Base64.encode64(object.attributes_for_signing[:body] || '')
484
- }
485
- begin
486
- response = mauth_connection.post("/mauth/#{mauth_api_version}/authentication_tickets.json", "authentication_ticket" => authentication_ticket)
487
- rescue ::Faraday::Error::ConnectionFailed, ::Faraday::Error::TimeoutError
488
- raise UnableToAuthenticateError, "mAuth service did not respond; received #{$!.class}: #{$!.message}"
489
- end
490
- if (200..299).cover?(response.status)
491
- nil
492
- elsif response.status == 412 || response.status == 404
493
- # the mAuth service responds with 412 when the given request is not authentically signed.
494
- # older versions of the mAuth service respond with 404 when the given app_uuid
495
- # does not exist, which is also considered to not be authentically signed. newer
496
- # versions of the service respond 412 in all cases, so the 404 check may be removed
497
- # when the old version of the mAuth service is out of service.
498
- raise InauthenticError, "The mAuth service responded with #{response.status}: #{response.body}"
499
- else
500
- mauth_service_response_error(response)
501
- end
240
+ @config[path] ||= YAML.load_file(path)
241
+ unless @config[path]
242
+ raise "File #{path} does not contain proper YAML information. Visit #{GITHUB_URL} for details."
502
243
  end
503
244
 
504
- def mauth_connection
505
- require 'faraday'
506
- require 'faraday_middleware'
507
- @mauth_connection ||= ::Faraday.new(mauth_baseurl, faraday_options) do |builder|
508
- builder.use MAuth::Faraday::MAuthClientUserAgent
509
- builder.use FaradayMiddleware::EncodeJson
510
- builder.adapter ::Faraday.default_adapter
511
- end
512
- end
245
+ @config[path]
513
246
  end
514
247
  end
515
248
  end
@@ -5,6 +5,8 @@ common: &common
5
5
  mauth_api_version: v1
6
6
  app_uuid: <%= configured.mauth_app_uuid! || 'fb17460e-9868-11e1-8399-0090f5ccb4d3' %>
7
7
  private_key_file: config/mauth_key
8
+ v2_only_authenticate: <%= configured.v2_only_authenticate || 'false' %>
9
+ v2_only_sign_requests: <%= configured.v2_only_sign_requests || 'false' %>
8
10
 
9
11
  production:
10
12
  <<: *common
@@ -0,0 +1,29 @@
1
+ module MAuth
2
+ # mAuth client was unable to verify the authenticity of a signed object (this does NOT mean the
3
+ # object is inauthentic). typically due to a failure communicating with the mAuth service, in
4
+ # which case the error may include the attribute mauth_service_response - a response from
5
+ # the mauth service (if it was contactable at all), which may contain more information about
6
+ # the error.
7
+ class UnableToAuthenticateError < StandardError
8
+ # the response from the MAuth service encountered when attempting to retrieve authentication
9
+ attr_accessor :mauth_service_response
10
+ end
11
+
12
+ # used to indicate that an object was expected to be validly signed but its signature does not
13
+ # match its contents, and so is inauthentic.
14
+ class InauthenticError < StandardError; end
15
+
16
+ # Used when the incoming request does not contain any mAuth related information
17
+ class MAuthNotPresent < StandardError; end
18
+
19
+ # required information for signing was missing
20
+ class UnableToSignError < StandardError; end
21
+
22
+ # used when an object has the V1 headers but not the V2 headers and the
23
+ # V2_ONLY_AUTHENTICATE variable is set to true.
24
+ class MissingV2Error < StandardError; end
25
+
26
+ class Client
27
+ class ConfigurationError < StandardError; end
28
+ end
29
+ end
@@ -26,8 +26,10 @@ module MAuth
26
26
  def call(env)
27
27
  retval = if should_authenticate?(env)
28
28
  mauth_request = MAuth::Rack::Request.new(env)
29
+ env['mauth.protocol_version'] = mauth_request.protocol_version
30
+
29
31
  if self.class.is_authentic?
30
- @app.call(env.merge('mauth.app_uuid' => mauth_request.signature_app_uuid, 'mauth.authentic' => true))
32
+ @app.call(env.merge!('mauth.app_uuid' => mauth_request.signature_app_uuid, 'mauth.authentic' => true))
31
33
  else
32
34
  response_for_inauthentic_request(env)
33
35
  end
data/lib/mauth/faraday.rb CHANGED
@@ -36,9 +36,15 @@ module MAuth
36
36
  end
37
37
 
38
38
  def attributes_for_signing
39
- request_url = @request_env[:url].path
40
- request_url = '/' if request_url.empty?
41
- @attributes_for_signing ||= { verb: @request_env[:method].to_s.upcase, request_url: request_url, body: @request_env[:body] }
39
+ @attributes_for_signing ||= begin
40
+ request_url = @request_env[:url].path.empty? ? '/' : @request_env[:url].path
41
+ {
42
+ verb: @request_env[:method].to_s.upcase,
43
+ request_url: request_url,
44
+ body: @request_env[:body],
45
+ query_string: @request_env[:url].query
46
+ }
47
+ end
42
48
  end
43
49
 
44
50
  # takes a Hash of headers; returns an instance of this class whose
@@ -68,6 +74,14 @@ module MAuth
68
74
  def x_mws_authentication
69
75
  @response_env[:response_headers]['x-mws-authentication']
70
76
  end
77
+
78
+ def mcc_time
79
+ @response_env[:response_headers]['mcc-time']
80
+ end
81
+
82
+ def mcc_authentication
83
+ @response_env[:response_headers]['mcc-authentication']
84
+ end
71
85
  end
72
86
 
73
87
  # add MAuth-Client's user-agent to a request