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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +5 -9
- data/CHANGELOG.md +5 -5
- data/CONTRIBUTING.md +8 -0
- data/README.md +18 -6
- data/Rakefile +107 -0
- data/exe/mauth-client +19 -19
- data/lib/mauth/client/authenticator_base.rb +118 -0
- data/lib/mauth/client/local_authenticator.rb +137 -0
- data/lib/mauth/client/remote_authenticator.rb +75 -0
- data/lib/mauth/client/security_token_cacher.rb +71 -0
- data/lib/mauth/client/signer.rb +67 -0
- data/lib/mauth/client.rb +99 -366
- data/lib/mauth/dice_bag/mauth.yml.dice +2 -0
- data/lib/mauth/errors.rb +29 -0
- data/lib/mauth/fake/rack.rb +3 -1
- data/lib/mauth/faraday.rb +17 -3
- data/lib/mauth/rack.rb +60 -16
- data/lib/mauth/request_and_response.rb +115 -8
- data/lib/mauth/version.rb +1 -1
- data/mauth-client.gemspec +3 -3
- metadata +29 -41
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
30
|
-
|
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
|
-
|
78
|
-
|
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
|
-
{
|
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
|
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
|
-
|
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
|
-
|
23
|
-
|
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
|
-
#
|
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 =
|
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 = [
|
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 = [
|
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
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', '
|
22
|
-
spec.add_dependency 'faraday_middleware', '
|
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
|
+
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:
|
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: '
|
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: '
|
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.
|
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.
|