mauth-client 6.4.3 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +6 -3
- data/.ruby-version +1 -1
- data/.travis.yml +3 -11
- data/Appraisals +1 -5
- data/CHANGELOG.md +15 -0
- data/Gemfile +16 -0
- data/README.md +81 -44
- data/Rakefile +20 -12
- data/UPGRADE_GUIDE.md +21 -0
- data/doc/mauth-client_CLI.md +1 -11
- data/examples/Gemfile +0 -1
- data/examples/README.md +14 -13
- data/examples/get_country_info.rb +44 -0
- data/exe/mauth-client +1 -23
- data/gemfiles/faraday_1.x.gemfile +17 -1
- data/gemfiles/faraday_2.x.gemfile +16 -0
- data/lib/mauth/client/{local_authenticator.rb → authenticator.rb} +124 -3
- data/lib/mauth/client/security_token_cacher.rb +20 -13
- data/lib/mauth/client.rb +21 -101
- data/lib/mauth/config_env.rb +81 -0
- data/lib/mauth/private_key_helper.rb +30 -0
- data/lib/mauth/version.rb +1 -1
- data/mauth-client.gemspec +5 -17
- metadata +30 -198
- data/.fossa.yml +0 -14
- data/doc/mauth.yml.md +0 -84
- data/examples/Gemfile.lock +0 -69
- data/examples/config.yml +0 -12
- data/examples/get_user_info.rb +0 -58
- data/gemfiles/faraday_0.x.gemfile +0 -7
- data/lib/mauth/client/authenticator_base.rb +0 -133
- data/lib/mauth/client/remote_authenticator.rb +0 -85
- data/lib/mauth/dice_bag/mauth.rb.dice +0 -12
- data/lib/mauth/dice_bag/mauth.yml.dice +0 -18
- data/lib/mauth/dice_bag/mauth_key.dice +0 -1
- data/lib/mauth/dice_bag/mauth_templates.rb +0 -21
@@ -4,14 +4,135 @@ require 'mauth/client/security_token_cacher'
|
|
4
4
|
require 'mauth/client/signer'
|
5
5
|
require 'openssl'
|
6
6
|
|
7
|
-
# methods to verify the authenticity of signed requests and responses
|
8
|
-
# public keys from the mAuth service as needed
|
7
|
+
# methods to verify the authenticity of signed requests and responses
|
9
8
|
|
10
9
|
module MAuth
|
11
10
|
class Client
|
12
|
-
module
|
11
|
+
module Authenticator
|
12
|
+
ALLOWED_DRIFT_SECONDS = 300
|
13
|
+
|
14
|
+
# takes an incoming request or response object, and returns whether
|
15
|
+
# the object is authentic according to its signature.
|
16
|
+
def authentic?(object)
|
17
|
+
log_authentication_request(object)
|
18
|
+
begin
|
19
|
+
authenticate!(object)
|
20
|
+
true
|
21
|
+
rescue InauthenticError, MAuthNotPresent, MissingV2Error
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# raises InauthenticError unless the given object is authentic. Will only
|
27
|
+
# authenticate with v2 if the environment variable V2_ONLY_AUTHENTICATE
|
28
|
+
# is set. Otherwise will fall back to v1 when v2 authentication fails
|
29
|
+
def authenticate!(object)
|
30
|
+
case object.protocol_version
|
31
|
+
when 2
|
32
|
+
begin
|
33
|
+
authenticate_v2!(object)
|
34
|
+
rescue InauthenticError => e
|
35
|
+
raise e if v2_only_authenticate?
|
36
|
+
raise e if disable_fallback_to_v1_on_v2_failure?
|
37
|
+
|
38
|
+
object.fall_back_to_mws_signature_info
|
39
|
+
raise e unless object.signature
|
40
|
+
|
41
|
+
log_authentication_request(object)
|
42
|
+
authenticate_v1!(object)
|
43
|
+
logger.warn('Completed successful authentication attempt after fallback to v1')
|
44
|
+
end
|
45
|
+
when 1
|
46
|
+
if v2_only_authenticate?
|
47
|
+
# If v2 is required but not present and v1 is present we raise MissingV2Error
|
48
|
+
msg = 'This service requires mAuth v2 mcc-authentication header but only v1 x-mws-authentication is present'
|
49
|
+
logger.error(msg)
|
50
|
+
raise MissingV2Error, msg
|
51
|
+
end
|
52
|
+
|
53
|
+
authenticate_v1!(object)
|
54
|
+
else
|
55
|
+
sub_str = v2_only_authenticate? ? '' : 'X-MWS-Authentication header is blank, '
|
56
|
+
msg = "Authentication Failed. No mAuth signature present; #{sub_str}MCC-Authentication header is blank."
|
57
|
+
logger.warn("mAuth signature not present on #{object.class}. Exception: #{msg}")
|
58
|
+
raise MAuthNotPresent, msg
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
13
62
|
private
|
14
63
|
|
64
|
+
# NOTE: This log is likely consumed downstream and the contents SHOULD NOT
|
65
|
+
# be changed without a thorough review of downstream consumers.
|
66
|
+
def log_authentication_request(object)
|
67
|
+
object_app_uuid = object.signature_app_uuid || '[none provided]'
|
68
|
+
object_token = object.signature_token || '[none provided]'
|
69
|
+
logger.info(
|
70
|
+
'Mauth-client attempting to authenticate request from app with mauth ' \
|
71
|
+
"app uuid #{object_app_uuid} to app with mauth app uuid #{client_app_uuid} " \
|
72
|
+
"using version #{object_token}."
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def log_inauthentic(object, message)
|
77
|
+
logger.error("mAuth signature authentication failed for #{object.class}. Exception: #{message}")
|
78
|
+
end
|
79
|
+
|
80
|
+
def time_within_valid_range!(object, time_signed, now = Time.now)
|
81
|
+
return if (-ALLOWED_DRIFT_SECONDS..ALLOWED_DRIFT_SECONDS).cover?(now.to_i - time_signed)
|
82
|
+
|
83
|
+
msg = "Time verification failed. #{time_signed} not within #{ALLOWED_DRIFT_SECONDS} of #{now}"
|
84
|
+
log_inauthentic(object, msg)
|
85
|
+
raise InauthenticError, msg
|
86
|
+
end
|
87
|
+
|
88
|
+
# V1 helpers
|
89
|
+
def authenticate_v1!(object)
|
90
|
+
time_valid_v1!(object)
|
91
|
+
token_valid_v1!(object)
|
92
|
+
signature_valid_v1!(object)
|
93
|
+
end
|
94
|
+
|
95
|
+
def time_valid_v1!(object)
|
96
|
+
if object.x_mws_time.nil?
|
97
|
+
msg = 'Time verification failed. No x-mws-time present.'
|
98
|
+
log_inauthentic(object, msg)
|
99
|
+
raise InauthenticError, msg
|
100
|
+
end
|
101
|
+
time_within_valid_range!(object, object.x_mws_time.to_i)
|
102
|
+
end
|
103
|
+
|
104
|
+
def token_valid_v1!(object)
|
105
|
+
return if object.signature_token == MWS_TOKEN
|
106
|
+
|
107
|
+
msg = "Token verification failed. Expected #{MWS_TOKEN}; token was #{object.signature_token}"
|
108
|
+
log_inauthentic(object, msg)
|
109
|
+
raise InauthenticError, msg
|
110
|
+
end
|
111
|
+
|
112
|
+
# V2 helpers
|
113
|
+
def authenticate_v2!(object)
|
114
|
+
time_valid_v2!(object)
|
115
|
+
token_valid_v2!(object)
|
116
|
+
signature_valid_v2!(object)
|
117
|
+
end
|
118
|
+
|
119
|
+
def time_valid_v2!(object)
|
120
|
+
if object.mcc_time.nil?
|
121
|
+
msg = 'Time verification failed. No MCC-Time present.'
|
122
|
+
log_inauthentic(object, msg)
|
123
|
+
raise InauthenticError, msg
|
124
|
+
end
|
125
|
+
time_within_valid_range!(object, object.mcc_time.to_i)
|
126
|
+
end
|
127
|
+
|
128
|
+
def token_valid_v2!(object)
|
129
|
+
return if object.signature_token == MWSV2_TOKEN
|
130
|
+
|
131
|
+
msg = "Token verification failed. Expected #{MWSV2_TOKEN}; token was #{object.signature_token}"
|
132
|
+
log_inauthentic(object, msg)
|
133
|
+
raise InauthenticError, msg
|
134
|
+
end
|
135
|
+
|
15
136
|
def signature_valid_v1!(object)
|
16
137
|
# We are in an unfortunate situation in which Euresource is percent-encoding parts of paths, but not
|
17
138
|
# all of them. In particular, Euresource is percent-encoding all special characters save for '/'.
|
@@ -1,16 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'faraday-http-cache'
|
4
|
+
require 'faraday/retry'
|
5
|
+
if Gem::Version.new(Faraday::VERSION) >= Gem::Version.new('2.0')
|
6
|
+
require 'faraday/net_http_persistent'
|
7
|
+
else
|
8
|
+
require 'net/http/persistent'
|
9
|
+
end
|
4
10
|
require 'mauth/faraday'
|
5
11
|
|
6
12
|
module MAuth
|
7
13
|
class Client
|
8
|
-
module
|
14
|
+
module Authenticator
|
9
15
|
class SecurityTokenCacher
|
16
|
+
attr_reader :mauth_client
|
17
|
+
|
10
18
|
def initialize(mauth_client)
|
11
19
|
@mauth_client = mauth_client
|
12
20
|
# TODO: should this be UnableToSignError?
|
13
|
-
|
21
|
+
mauth_client.assert_private_key(
|
14
22
|
UnableToAuthenticateError.new('Cannot fetch public keys from mAuth service without a private key!')
|
15
23
|
)
|
16
24
|
end
|
@@ -19,7 +27,7 @@ module MAuth
|
|
19
27
|
# url-encode the app_uuid to prevent trickery like escaping upward with ../../ in a malicious
|
20
28
|
# app_uuid - probably not exploitable, but this is the right way to do it anyway.
|
21
29
|
url_encoded_app_uuid = CGI.escape(app_uuid)
|
22
|
-
path = "/mauth/#{
|
30
|
+
path = "/mauth/#{mauth_client.mauth_api_version}/security_tokens/#{url_encoded_app_uuid}.json"
|
23
31
|
response = signed_mauth_connection.get(path)
|
24
32
|
|
25
33
|
case response.status
|
@@ -29,11 +37,11 @@ module MAuth
|
|
29
37
|
# signing with a key mAuth doesn't know about is considered inauthentic
|
30
38
|
raise InauthenticError, "mAuth service responded with 404 looking up public key for #{app_uuid}"
|
31
39
|
else
|
32
|
-
|
40
|
+
mauth_client.send(:mauth_service_response_error, response)
|
33
41
|
end
|
34
42
|
rescue ::Faraday::ConnectionFailed, ::Faraday::TimeoutError => e
|
35
43
|
msg = "mAuth service did not respond; received #{e.class}: #{e.message}"
|
36
|
-
|
44
|
+
mauth_client.logger.error("Unable to authenticate with MAuth. Exception #{msg}")
|
37
45
|
raise UnableToAuthenticateError, msg
|
38
46
|
end
|
39
47
|
|
@@ -43,21 +51,20 @@ module MAuth
|
|
43
51
|
JSON.parse response_body
|
44
52
|
rescue JSON::ParserError => e
|
45
53
|
msg = "mAuth service responded with unparseable json: #{response_body}\n#{e.class}: #{e.message}"
|
46
|
-
|
54
|
+
mauth_client.logger.error("Unable to authenticate with MAuth. Exception #{msg}")
|
47
55
|
raise UnableToAuthenticateError, msg
|
48
56
|
end
|
49
57
|
|
50
58
|
def signed_mauth_connection
|
51
59
|
@signed_mauth_connection ||= begin
|
52
|
-
if
|
53
|
-
@mauth_client.faraday_options[:ssl] = { ca_path: @mauth_client.ssl_certs_path }
|
54
|
-
end
|
60
|
+
mauth_client.faraday_options[:ssl] = { ca_path: mauth_client.ssl_certs_path } if mauth_client.ssl_certs_path
|
55
61
|
|
56
|
-
::Faraday.new(
|
62
|
+
::Faraday.new(mauth_client.mauth_baseurl, mauth_client.faraday_options) do |builder|
|
57
63
|
builder.use MAuth::Faraday::MAuthClientUserAgent
|
58
|
-
builder.use MAuth::Faraday::RequestSigner, 'mauth_client' =>
|
59
|
-
builder.use :http_cache, logger:
|
60
|
-
builder.
|
64
|
+
builder.use MAuth::Faraday::RequestSigner, 'mauth_client' => mauth_client
|
65
|
+
builder.use :http_cache, store: mauth_client.cache_store, logger: mauth_client.logger, shared_cache: false
|
66
|
+
builder.request :retry, max: 2
|
67
|
+
builder.adapter :net_http_persistent
|
61
68
|
end
|
62
69
|
end
|
63
70
|
end
|
data/lib/mauth/client.rb
CHANGED
@@ -7,13 +7,12 @@ require 'json'
|
|
7
7
|
require 'yaml'
|
8
8
|
require 'mauth/core_ext'
|
9
9
|
require 'mauth/autoload'
|
10
|
-
require 'mauth/dice_bag/mauth_templates'
|
11
10
|
require 'mauth/version'
|
12
|
-
require 'mauth/client/
|
13
|
-
require 'mauth/client/local_authenticator'
|
14
|
-
require 'mauth/client/remote_authenticator'
|
11
|
+
require 'mauth/client/authenticator'
|
15
12
|
require 'mauth/client/signer'
|
13
|
+
require 'mauth/config_env'
|
16
14
|
require 'mauth/errors'
|
15
|
+
require 'mauth/private_key_helper'
|
17
16
|
|
18
17
|
module MAuth
|
19
18
|
# does operations which require a private key and corresponding app uuid. this is primarily:
|
@@ -31,66 +30,22 @@ module MAuth
|
|
31
30
|
AUTH_HEADER_DELIMITER = ';'
|
32
31
|
RACK_ENV_APP_UUID_KEY = 'mauth.app_uuid'
|
33
32
|
|
34
|
-
include
|
33
|
+
include Authenticator
|
35
34
|
include Signer
|
36
35
|
|
37
36
|
# returns a configuration (to be passed to MAuth::Client.new) which is configured from information stored in
|
38
37
|
# standard places. all of which is overridable by options in case some defaults do not apply.
|
39
38
|
#
|
40
39
|
# options (may be symbols or strings) - any or all may be omitted where your usage conforms to the defaults.
|
41
|
-
# -
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# tries Rails.environment, ENV['RAILS_ENV'], and ENV['RACK_ENV'], and falls back to 'development' if none
|
45
|
-
# of these are set.
|
46
|
-
# - mauth_config - MAuth configuration. defaults to load this from a yaml file (see mauth_config_yml option)
|
47
|
-
# which is assumed to be keyed with the environment at the root. if this is specified, no yaml file is
|
48
|
-
# loaded, and the given config is passed through with any other defaults applied. at the moment, the only
|
49
|
-
# other default is to set the logger.
|
50
|
-
# - mauth_config_yml - specifies where a mauth configuration yaml file can be found. by default checks
|
51
|
-
# ENV['MAUTH_CONFIG_YML'] or a file 'config/mauth.yml' relative to the root.
|
40
|
+
# - mauth_config - MAuth configuration. defaults to load this from environment variables. if this is specified,
|
41
|
+
# no environment variable is loaded, and the given config is passed through with any other defaults applied.
|
42
|
+
# at the moment, the only other default is to set the logger.
|
52
43
|
# - logger - by default checks ::Rails.logger
|
53
44
|
def self.default_config(options = {})
|
54
45
|
options = options.stringify_symbol_keys
|
55
46
|
|
56
|
-
# find
|
57
|
-
|
58
|
-
app_root = options['root'] || begin
|
59
|
-
if Object.const_defined?(:Rails) && ::Rails.respond_to?(:root) && ::Rails.root
|
60
|
-
Rails.root
|
61
|
-
else
|
62
|
-
ENV['RAILS_ROOT'] || ENV['RACK_ROOT'] || ENV['APP_ROOT'] || '.'
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# find the environment (with which yaml files are keyed)
|
67
|
-
env = options['environment'] || begin
|
68
|
-
if Object.const_defined?(:Rails) && ::Rails.respond_to?(:environment)
|
69
|
-
Rails.environment
|
70
|
-
else
|
71
|
-
ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# find mauth config, given on options, or in a file at
|
76
|
-
# ENV['MAUTH_CONFIG_YML'] or config/mauth.yml in the app_root
|
77
|
-
mauth_config = options['mauth_config'] || begin
|
78
|
-
mauth_config_yml = options['mauth_config_yml']
|
79
|
-
mauth_config_yml ||= ENV['MAUTH_CONFIG_YML']
|
80
|
-
default_loc = 'config/mauth.yml'
|
81
|
-
default_yml = File.join(app_root, default_loc)
|
82
|
-
mauth_config_yml ||= default_yml if File.exist?(default_yml)
|
83
|
-
if mauth_config_yml && File.exist?(mauth_config_yml)
|
84
|
-
whole_config = ConfigFile.load(mauth_config_yml)
|
85
|
-
errmessage = "#{mauth_config_yml} config has no key #{env} - it has keys #{whole_config.keys.inspect}"
|
86
|
-
whole_config[env] || raise(MAuth::Client::ConfigurationError, errmessage)
|
87
|
-
else
|
88
|
-
raise MAuth::Client::ConfigurationError,
|
89
|
-
'could not find mauth config yaml file. this file may be ' \
|
90
|
-
"placed in #{default_loc}, specified with the mauth_config_yml option, or specified with the " \
|
91
|
-
'MAUTH_CONFIG_YML environment variable.'
|
92
|
-
end
|
93
|
-
end
|
47
|
+
# find mauth config
|
48
|
+
mauth_config = options['mauth_config'] || ConfigEnv.load
|
94
49
|
|
95
50
|
unless mauth_config.key?('logger')
|
96
51
|
# the logger. Rails.logger if it exists, otherwise, no logger
|
@@ -106,18 +61,13 @@ module MAuth
|
|
106
61
|
|
107
62
|
# new client with the given App UUID and public key. config may include the following (all
|
108
63
|
# config keys may be strings or symbols):
|
109
|
-
# - private_key - required for signing and for
|
110
|
-
#
|
111
|
-
# MAuth::Rack::RequestAuthenticator). may be given as a string or a OpenSSL::PKey::RSA
|
112
|
-
# instance.
|
64
|
+
# - private_key - required for signing and for authentication.
|
65
|
+
# may be given as a string or a OpenSSL::PKey::RSA instance.
|
113
66
|
# - app_uuid - required in the same circumstances where a private_key is required
|
114
|
-
# - mauth_baseurl - required. needed
|
115
|
-
# for remote authentication for hopefully obvious reasons.
|
67
|
+
# - mauth_baseurl - required. needed to retrieve public keys.
|
116
68
|
# - mauth_api_version - required. only 'v1' exists / is supported as of this writing.
|
117
69
|
# - logger - a Logger to which any useful information will be written. if this is omitted and
|
118
70
|
# Rails.logger exists, that will be used.
|
119
|
-
# - authenticator - this pretty much never needs to be specified. LocalAuthenticator or
|
120
|
-
# RemoteRequestAuthenticator will be used as appropriate.
|
121
71
|
def initialize(config = {})
|
122
72
|
# stringify symbol keys
|
123
73
|
given_config = config.stringify_symbol_keys
|
@@ -131,7 +81,7 @@ module MAuth
|
|
131
81
|
when nil
|
132
82
|
nil
|
133
83
|
when String
|
134
|
-
|
84
|
+
PrivateKeyHelper.load(given_config['private_key'])
|
135
85
|
when OpenSSL::PKey::RSA
|
136
86
|
given_config['private_key']
|
137
87
|
else
|
@@ -153,31 +103,20 @@ module MAuth
|
|
153
103
|
end
|
154
104
|
end
|
155
105
|
|
156
|
-
request_config = { timeout: 10, open_timeout:
|
106
|
+
request_config = { timeout: 10, open_timeout: 3 }
|
157
107
|
request_config.merge!(symbolize_keys(given_config['faraday_options'])) if given_config['faraday_options']
|
158
108
|
@config['faraday_options'] = { request: request_config } || {}
|
159
109
|
@config['ssl_certs_path'] = given_config['ssl_certs_path'] if given_config['ssl_certs_path']
|
160
110
|
@config['v2_only_authenticate'] = given_config['v2_only_authenticate'].to_s.casecmp('true').zero?
|
161
111
|
@config['v2_only_sign_requests'] = given_config['v2_only_sign_requests'].to_s.casecmp('true').zero?
|
162
|
-
@config['disable_fallback_to_v1_on_v2_failure'] =
|
163
|
-
given_config['disable_fallback_to_v1_on_v2_failure'].to_s.casecmp('true').zero?
|
164
112
|
@config['v1_only_sign_requests'] = given_config['v1_only_sign_requests'].to_s.casecmp('true').zero?
|
165
|
-
|
166
113
|
if @config['v2_only_sign_requests'] && @config['v1_only_sign_requests']
|
167
114
|
raise MAuth::Client::ConfigurationError, 'v2_only_sign_requests and v1_only_sign_requests may not both be true'
|
168
115
|
end
|
169
116
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
elsif client_app_uuid && private_key
|
174
|
-
@config['authenticator'] = LocalAuthenticator
|
175
|
-
# MAuth::Client can authenticate locally if it's provided a client_app_uuid and private_key
|
176
|
-
else
|
177
|
-
# otherwise, it will authenticate remotely (requests only)
|
178
|
-
@config['authenticator'] = RemoteRequestAuthenticator
|
179
|
-
end
|
180
|
-
extend @config['authenticator'] if @config['authenticator']
|
117
|
+
@config['disable_fallback_to_v1_on_v2_failure'] =
|
118
|
+
given_config['disable_fallback_to_v1_on_v2_failure'].to_s.casecmp('true').zero?
|
119
|
+
@config['use_rails_cache'] = given_config['use_rails_cache']
|
181
120
|
end
|
182
121
|
|
183
122
|
def logger
|
@@ -228,6 +167,10 @@ module MAuth
|
|
228
167
|
raise err unless private_key
|
229
168
|
end
|
230
169
|
|
170
|
+
def cache_store
|
171
|
+
Rails.cache if @config['use_rails_cache'] && Object.const_defined?(:Rails) && ::Rails.respond_to?(:cache)
|
172
|
+
end
|
173
|
+
|
231
174
|
private
|
232
175
|
|
233
176
|
def mauth_service_response_error(response)
|
@@ -246,27 +189,4 @@ module MAuth
|
|
246
189
|
hash
|
247
190
|
end
|
248
191
|
end
|
249
|
-
|
250
|
-
module ConfigFile
|
251
|
-
GITHUB_URL = 'https://github.com/mdsol/mauth-client-ruby'
|
252
|
-
@config = {}
|
253
|
-
|
254
|
-
def self.load(path)
|
255
|
-
unless File.exist?(path)
|
256
|
-
raise "File #{path} not found. Please visit #{GITHUB_URL} for details."
|
257
|
-
end
|
258
|
-
|
259
|
-
@config[path] ||= yaml_safe_load_file(path)
|
260
|
-
unless @config[path]
|
261
|
-
raise "File #{path} does not contain proper YAML information. Visit #{GITHUB_URL} for details."
|
262
|
-
end
|
263
|
-
|
264
|
-
@config[path]
|
265
|
-
end
|
266
|
-
|
267
|
-
def self.yaml_safe_load_file(path)
|
268
|
-
yml_data = File.read(path)
|
269
|
-
YAML.safe_load(yml_data, aliases: true)
|
270
|
-
end
|
271
|
-
end
|
272
192
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MAuth
|
4
|
+
class ConfigEnv
|
5
|
+
GITHUB_URL = 'https://github.com/mdsol/mauth-client-ruby'
|
6
|
+
|
7
|
+
ENV_STUFF = {
|
8
|
+
'MAUTH_URL' => nil,
|
9
|
+
'MAUTH_API_VERSION' => 'v1',
|
10
|
+
'MAUTH_APP_UUID' => nil,
|
11
|
+
'MAUTH_PRIVATE_KEY' => nil,
|
12
|
+
'MAUTH_PRIVATE_KEY_FILE' => 'config/mauth_key',
|
13
|
+
'MAUTH_V2_ONLY_AUTHENTICATE' => false,
|
14
|
+
'MAUTH_V2_ONLY_SIGN_REQUESTS' => false,
|
15
|
+
'MAUTH_DISABLE_FALLBACK_TO_V1_ON_V2_FAILURE' => false,
|
16
|
+
'MAUTH_V1_ONLY_SIGN_REQUESTS' => true,
|
17
|
+
'MAUTH_USE_RAILS_CACHE' => false
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def load
|
22
|
+
validate! if production?
|
23
|
+
|
24
|
+
{
|
25
|
+
'mauth_baseurl' => env[:mauth_url] || 'http://localhost:7000',
|
26
|
+
'mauth_api_version' => env[:mauth_api_version],
|
27
|
+
'app_uuid' => env[:mauth_app_uuid] || 'fb17460e-9868-11e1-8399-0090f5ccb4d3',
|
28
|
+
'private_key' => private_key || PrivateKeyHelper.generate.to_s,
|
29
|
+
'v2_only_authenticate' => env[:mauth_v2_only_authenticate],
|
30
|
+
'v2_only_sign_requests' => env[:mauth_v2_only_sign_requests],
|
31
|
+
'disable_fallback_to_v1_on_v2_failure' => env[:mauth_disable_fallback_to_v1_on_v2_failure],
|
32
|
+
'v1_only_sign_requests' => env[:mauth_v1_only_sign_requests],
|
33
|
+
'use_rails_cache' => env[:mauth_use_rails_cache]
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def validate!
|
40
|
+
errors = []
|
41
|
+
errors << 'The MAUTH_URL environment variable must be set' if env[:mauth_url].nil?
|
42
|
+
errors << 'The MAUTH_APP_UUID environment variable must be set' if env[:mauth_app_uuid].nil?
|
43
|
+
errors << 'The MAUTH_PRIVATE_KEY environment variable must be set' if env[:mauth_private_key].nil?
|
44
|
+
return if errors.empty?
|
45
|
+
|
46
|
+
errors.map! { |err| "#{err} => See #{GITHUB_URL}" }
|
47
|
+
errors.unshift('Invalid MAuth Client configuration:')
|
48
|
+
raise errors.join("\n")
|
49
|
+
end
|
50
|
+
|
51
|
+
def env
|
52
|
+
@env ||= ENV_STUFF.each_with_object({}) do |(key, default), hsh|
|
53
|
+
env_key = key.downcase.to_sym
|
54
|
+
hsh[env_key] = ENV.fetch(key, default)
|
55
|
+
|
56
|
+
case default
|
57
|
+
when TrueClass, FalseClass
|
58
|
+
hsh[env_key] = hsh[env_key].to_s.casecmp('true').zero?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def production?
|
64
|
+
environment.to_s.casecmp('production').zero?
|
65
|
+
end
|
66
|
+
|
67
|
+
def environment
|
68
|
+
return Rails.environment if Object.const_defined?(:Rails) && ::Rails.respond_to?(:environment)
|
69
|
+
|
70
|
+
ENV.fetch('RAILS_ENV') { ENV.fetch('RACK_ENV', 'development') }
|
71
|
+
end
|
72
|
+
|
73
|
+
def private_key
|
74
|
+
return env[:mauth_private_key] if env[:mauth_private_key]
|
75
|
+
return nil unless env[:mauth_private_key_file] && File.readable?(env[:mauth_private_key_file])
|
76
|
+
|
77
|
+
File.read(env[:mauth_private_key_file])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module MAuth
|
6
|
+
module PrivateKeyHelper
|
7
|
+
HEADER = '-----BEGIN RSA PRIVATE KEY-----'
|
8
|
+
FOOTER = '-----END RSA PRIVATE KEY-----'
|
9
|
+
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def generate
|
13
|
+
OpenSSL::PKey::RSA.generate(2048)
|
14
|
+
end
|
15
|
+
|
16
|
+
def load(key)
|
17
|
+
OpenSSL::PKey::RSA.new(to_rsa_format(key))
|
18
|
+
rescue OpenSSL::PKey::RSAError
|
19
|
+
raise 'The private key provided is invalid'
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_rsa_format(key)
|
23
|
+
return key if key.include?("\n")
|
24
|
+
|
25
|
+
body = key.strip.delete_prefix(HEADER).delete_suffix(FOOTER).strip
|
26
|
+
body = body.include?("\s") ? body.tr("\s", "\n") : body.scan(/.{1,64}/).join("\n")
|
27
|
+
"#{HEADER}\n#{body}\n#{FOOTER}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/mauth/version.rb
CHANGED
data/mauth-client.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
'Includes middleware for Rack and Faraday for incoming and outgoing requests and responses.'
|
15
15
|
spec.homepage = 'https://github.com/mdsol/mauth-client-ruby'
|
16
16
|
spec.license = 'MIT'
|
17
|
-
spec.required_ruby_version = '>= 2.
|
17
|
+
spec.required_ruby_version = '>= 2.7.0'
|
18
18
|
|
19
19
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
20
|
spec.bindir = 'exe'
|
@@ -23,23 +23,11 @@ Gem::Specification.new do |spec|
|
|
23
23
|
|
24
24
|
spec.add_dependency 'addressable', '~> 2.0'
|
25
25
|
spec.add_dependency 'coderay', '~> 1.0'
|
26
|
-
spec.add_dependency '
|
27
|
-
spec.add_dependency 'faraday', '>= 0.9', '< 3.0'
|
26
|
+
spec.add_dependency 'faraday', '>= 1.9', '< 3.0'
|
28
27
|
spec.add_dependency 'faraday-http-cache', '>= 2.0', '< 3.0'
|
28
|
+
spec.add_dependency 'faraday-net_http_persistent'
|
29
|
+
spec.add_dependency 'faraday-retry'
|
30
|
+
spec.add_dependency 'net-http-persistent', '>= 3.1'
|
29
31
|
spec.add_dependency 'rack', '> 2.2.3'
|
30
32
|
spec.add_dependency 'term-ansicolor', '~> 1.0'
|
31
|
-
|
32
|
-
spec.add_development_dependency 'appraisal'
|
33
|
-
spec.add_development_dependency 'benchmark-ips', '~> 2.7'
|
34
|
-
spec.add_development_dependency 'bundler', '>= 1.17'
|
35
|
-
spec.add_development_dependency 'byebug'
|
36
|
-
spec.add_development_dependency 'rack-test', '~> 1.1.0'
|
37
|
-
spec.add_development_dependency 'rake', '~> 12.0'
|
38
|
-
spec.add_development_dependency 'rspec', '~> 3.8'
|
39
|
-
spec.add_development_dependency 'rubocop', '= 1.25.1'
|
40
|
-
spec.add_development_dependency 'rubocop-mdsol', '~> 0.1'
|
41
|
-
spec.add_development_dependency 'rubocop-performance', '= 1.13.2'
|
42
|
-
spec.add_development_dependency 'simplecov', '~> 0.16'
|
43
|
-
spec.add_development_dependency 'timecop', '~> 0.9'
|
44
|
-
spec.add_development_dependency 'webmock', '~> 3.0'
|
45
33
|
end
|