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/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