cerner-oauth1a 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/CONTRIBUTORS.md +1 -1
- data/lib/cerner/oauth1a/access_token.rb +51 -17
- data/lib/cerner/oauth1a/access_token_agent.rb +21 -5
- data/lib/cerner/oauth1a/oauth_error.rb +10 -2
- data/lib/cerner/oauth1a/protocol.rb +5 -2
- data/lib/cerner/oauth1a/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd0cf4f52bc3f90e247bc8c507415f540ababac1483da4c626294328857dc270
|
4
|
+
data.tar.gz: 2ae54c85c1ab06b6a3d9581cc062fae1a4a3857037131fbcb479812474f72756
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 429eb27f3433283a68643968c83ab8ff0b0322bd751ac8da475bb62978922dd4e78d110e3561128b91010a8998070b06d286ebd2ccaa87f730f72943340d69e6
|
7
|
+
data.tar.gz: 3ada2c0200c845f5710a7eacbf77fbea95a3171a9ca56dc1270265a5d2511c7ee7de62fb58371f23f7eb960ce846ba3b762cce0a9d1292938280eae0177be872
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# v2.1.0
|
2
|
+
Added an attribute for the Protection Realm to Cerner::OAuth1a::AccessTokenAgent,
|
3
|
+
Cerner::OAuth1a::AccessToken, and Cerner::OAuth1a::OAuthError. This value will be
|
4
|
+
parsed as the canonical root URI of the agent's configured access_token_url. When
|
5
|
+
this value is available, it will be added to errors and generated authorization
|
6
|
+
headers.
|
7
|
+
|
1
8
|
# v2.0.1
|
2
9
|
Allow parsing authorization headers that do not include an oauth_version parameter as per
|
3
10
|
the spec:
|
data/CONTRIBUTORS.md
CHANGED
@@ -48,7 +48,8 @@ module Cerner
|
|
48
48
|
timestamp: timestamp,
|
49
49
|
token: token,
|
50
50
|
signature_method: signature_method,
|
51
|
-
signature: signature
|
51
|
+
signature: signature,
|
52
|
+
realm: params[:realm]
|
52
53
|
)
|
53
54
|
end
|
54
55
|
|
@@ -74,6 +75,8 @@ module Cerner
|
|
74
75
|
# This value is only populated after a successful #authenticate and only if the #token (oauth_token)
|
75
76
|
# contains a 'Consumer.Principal' parameter.
|
76
77
|
attr_reader :consumer_principal
|
78
|
+
# Returns a String, but may be nil, with the Protection Realm related to this token.
|
79
|
+
attr_reader :realm
|
77
80
|
|
78
81
|
# Public: Constructs an instance.
|
79
82
|
#
|
@@ -93,6 +96,8 @@ module Cerner
|
|
93
96
|
# Defaults to PLAINTEXT.
|
94
97
|
# :signature - The optional String representing the signature.
|
95
98
|
# Defaults to nil.
|
99
|
+
# :realm - The optional String representing the protection realm.
|
100
|
+
# Defaults to nil.
|
96
101
|
#
|
97
102
|
# Raises ArgumentError if consumer_key, nonce, timestamp, token or signature_method is nil.
|
98
103
|
def initialize(
|
@@ -104,7 +109,8 @@ module Cerner
|
|
104
109
|
signature_method: 'PLAINTEXT',
|
105
110
|
timestamp:,
|
106
111
|
token:,
|
107
|
-
token_secret: nil
|
112
|
+
token_secret: nil,
|
113
|
+
realm: nil
|
108
114
|
)
|
109
115
|
raise ArgumentError, 'consumer_key is nil' unless consumer_key
|
110
116
|
raise ArgumentError, 'nonce is nil' unless nonce
|
@@ -122,6 +128,7 @@ module Cerner
|
|
122
128
|
@timestamp = convert_to_time(timestamp)
|
123
129
|
@token = token
|
124
130
|
@token_secret = token_secret || nil
|
131
|
+
@realm = realm || nil
|
125
132
|
end
|
126
133
|
|
127
134
|
# Public: Generates a value suitable for use as an HTTP Authorization header. If #signature is
|
@@ -136,7 +143,7 @@ module Cerner
|
|
136
143
|
return @authorization_header if @authorization_header
|
137
144
|
|
138
145
|
unless @signature_method == 'PLAINTEXT'
|
139
|
-
raise OAuthError.new('signature_method must be PLAINTEXT', nil, 'signature_method_rejected')
|
146
|
+
raise OAuthError.new('signature_method must be PLAINTEXT', nil, 'signature_method_rejected', nil, @realm)
|
140
147
|
end
|
141
148
|
|
142
149
|
if @signature
|
@@ -144,10 +151,11 @@ module Cerner
|
|
144
151
|
elsif @accessor_secret && @token_secret
|
145
152
|
sig = "#{@accessor_secret}&#{@token_secret}"
|
146
153
|
else
|
147
|
-
raise OAuthError.new('accessor_secret or token_secret is nil', nil, 'parameter_absent')
|
154
|
+
raise OAuthError.new('accessor_secret or token_secret is nil', nil, 'parameter_absent', nil, @realm)
|
148
155
|
end
|
149
156
|
|
150
157
|
tuples = {
|
158
|
+
realm: @realm,
|
151
159
|
oauth_version: '1.0',
|
152
160
|
oauth_signature_method: @signature_method,
|
153
161
|
oauth_signature: sig,
|
@@ -174,14 +182,21 @@ module Cerner
|
|
174
182
|
def authenticate(access_token_agent)
|
175
183
|
raise ArgumentError, 'access_token_agent is nil' unless access_token_agent
|
176
184
|
|
185
|
+
if @realm && !@realm.eql?(access_token_agent.realm)
|
186
|
+
raise OAuthError.new('realm does not match provider', nil, 'token_rejected', nil, access_token_agent.realm)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Set realm to the provider's realm if it's not already set
|
190
|
+
@realm ||= access_token_agent.realm
|
191
|
+
|
177
192
|
unless @signature_method == 'PLAINTEXT'
|
178
|
-
raise OAuthError.new('signature_method must be PLAINTEXT', nil, 'signature_method_rejected')
|
193
|
+
raise OAuthError.new('signature_method must be PLAINTEXT', nil, 'signature_method_rejected', nil, @realm)
|
179
194
|
end
|
180
195
|
|
181
196
|
tuples = Protocol.parse_url_query_string(@token)
|
182
197
|
|
183
198
|
unless @consumer_key == tuples.delete(:ConsumerKey)
|
184
|
-
raise OAuthError.new('consumer keys do not match', nil, 'consumer_key_rejected')
|
199
|
+
raise OAuthError.new('consumer keys do not match', nil, 'consumer_key_rejected', nil, @realm)
|
185
200
|
end
|
186
201
|
|
187
202
|
verify_expiration(tuples.delete(:ExpiresOn))
|
@@ -232,7 +247,8 @@ module Cerner
|
|
232
247
|
token == other.token &&
|
233
248
|
token_secret == other.token_secret &&
|
234
249
|
signature_method == other.signature_method &&
|
235
|
-
signature == other.signature
|
250
|
+
signature == other.signature &&
|
251
|
+
realm == other.realm
|
236
252
|
end
|
237
253
|
|
238
254
|
# Public: Compare this to other based on the attributes. Equivalent to calling #==.
|
@@ -258,7 +274,8 @@ module Cerner
|
|
258
274
|
token_secret: @token_secret,
|
259
275
|
signature_method: @signature_method,
|
260
276
|
signature: @signature,
|
261
|
-
consumer_principal: @consumer_principal
|
277
|
+
consumer_principal: @consumer_principal,
|
278
|
+
realm: @realm
|
262
279
|
}
|
263
280
|
end
|
264
281
|
|
@@ -290,7 +307,8 @@ module Cerner
|
|
290
307
|
'token missing ExpiresOn',
|
291
308
|
nil,
|
292
309
|
'oauth_parameters_rejected',
|
293
|
-
'oauth_token'
|
310
|
+
'oauth_token',
|
311
|
+
@realm
|
294
312
|
)
|
295
313
|
end
|
296
314
|
|
@@ -300,7 +318,9 @@ module Cerner
|
|
300
318
|
raise OAuthError.new(
|
301
319
|
'token has expired',
|
302
320
|
nil,
|
303
|
-
'token_expired'
|
321
|
+
'token_expired',
|
322
|
+
nil,
|
323
|
+
@realm
|
304
324
|
)
|
305
325
|
end
|
306
326
|
end
|
@@ -311,14 +331,21 @@ module Cerner
|
|
311
331
|
'token missing KeysVersion',
|
312
332
|
nil,
|
313
333
|
'oauth_parameters_rejected',
|
314
|
-
'oauth_token'
|
334
|
+
'oauth_token',
|
335
|
+
@realm
|
315
336
|
)
|
316
337
|
end
|
317
338
|
|
318
339
|
begin
|
319
340
|
access_token_agent.retrieve_keys(keys_version)
|
320
341
|
rescue OAuthError
|
321
|
-
raise OAuthError.new(
|
342
|
+
raise OAuthError.new(
|
343
|
+
'token references invalid keys version',
|
344
|
+
nil,
|
345
|
+
'oauth_parameters_rejected',
|
346
|
+
'oauth_token',
|
347
|
+
@realm
|
348
|
+
)
|
322
349
|
end
|
323
350
|
end
|
324
351
|
|
@@ -329,7 +356,7 @@ module Cerner
|
|
329
356
|
# Raises OAuthError if the parameter is not authentic
|
330
357
|
def verify_token(keys)
|
331
358
|
unless keys.verify_rsasha1_signature(@token)
|
332
|
-
raise OAuthError.new('token is not authentic', nil, 'oauth_parameters_rejected', 'oauth_token')
|
359
|
+
raise OAuthError.new('token is not authentic', nil, 'oauth_parameters_rejected', 'oauth_token', @realm)
|
333
360
|
end
|
334
361
|
end
|
335
362
|
|
@@ -341,8 +368,12 @@ module Cerner
|
|
341
368
|
# Raises OAuthError if there is no signature, the parameter is invalid or the signature does
|
342
369
|
# not match the secrets
|
343
370
|
def verify_signature(keys, hmac_secrets)
|
344
|
-
|
345
|
-
|
371
|
+
unless @signature
|
372
|
+
raise OAuthError.new('missing signature', nil, 'oauth_parameters_absent', 'oauth_signature', @realm)
|
373
|
+
end
|
374
|
+
unless hmac_secrets
|
375
|
+
raise OAuthError.new('missing HMACSecrets', nil, 'oauth_parameters_rejected', 'oauth_token', @realm)
|
376
|
+
end
|
346
377
|
|
347
378
|
begin
|
348
379
|
secrets = keys.decrypt_hmac_secrets(hmac_secrets)
|
@@ -351,14 +382,17 @@ module Cerner
|
|
351
382
|
"unable to decrypt HMACSecrets: #{e.message}",
|
352
383
|
nil,
|
353
384
|
'oauth_parameters_rejected',
|
354
|
-
'oauth_token'
|
385
|
+
'oauth_token',
|
386
|
+
@realm
|
355
387
|
)
|
356
388
|
end
|
357
389
|
|
358
390
|
secrets_parts = Protocol.parse_url_query_string(secrets)
|
359
391
|
expected_signature = "#{secrets_parts[:ConsumerSecret]}&#{secrets_parts[:TokenSecret]}"
|
360
392
|
|
361
|
-
|
393
|
+
unless @signature == expected_signature
|
394
|
+
raise OAuthError.new('signature is not valid', nil, 'signature_invalid', nil, @realm)
|
395
|
+
end
|
362
396
|
end
|
363
397
|
end
|
364
398
|
end
|
@@ -25,6 +25,8 @@ module Cerner
|
|
25
25
|
attr_reader :consumer_key
|
26
26
|
# Returns the String Consumer Secret.
|
27
27
|
attr_reader :consumer_secret
|
28
|
+
# Returns the String Protection Realm. The realm is root of the access_token_url (scheme + hostname).
|
29
|
+
attr_reader :realm
|
28
30
|
|
29
31
|
# Public: Constructs an instance of the agent.
|
30
32
|
#
|
@@ -72,6 +74,7 @@ module Cerner
|
|
72
74
|
@consumer_secret = consumer_secret
|
73
75
|
|
74
76
|
@access_token_url = convert_to_http_uri(access_token_url)
|
77
|
+
@realm = canonical_root_url_for(@access_token_url)
|
75
78
|
|
76
79
|
@open_timeout = (open_timeout ? open_timeout.to_i : 5)
|
77
80
|
@read_timeout = (read_timeout ? read_timeout.to_i : 5)
|
@@ -215,6 +218,18 @@ module Cerner
|
|
215
218
|
uri
|
216
219
|
end
|
217
220
|
|
221
|
+
# Internal: Returns a String containing the canonical root url.
|
222
|
+
#
|
223
|
+
# url - A URL to get the canonical root url String from.
|
224
|
+
#
|
225
|
+
# raises ArgumentError if url is nil.
|
226
|
+
def canonical_root_url_for(url)
|
227
|
+
raise ArgumentError, 'url is nil' unless url
|
228
|
+
|
229
|
+
realm = URI("#{url.scheme}://#{url.host}:#{url.port}")
|
230
|
+
realm.to_s
|
231
|
+
end
|
232
|
+
|
218
233
|
# Internal: Prepare a request for #retrieve
|
219
234
|
def retrieve_prepare_request(timestamp, nonce, accessor_secret, principal)
|
220
235
|
# construct a POST request
|
@@ -252,14 +267,15 @@ module Cerner
|
|
252
267
|
nonce: nonce,
|
253
268
|
timestamp: timestamp,
|
254
269
|
token: tuples[:oauth_token],
|
255
|
-
token_secret: tuples[:oauth_token_secret]
|
270
|
+
token_secret: tuples[:oauth_token_secret],
|
271
|
+
realm: @realm
|
256
272
|
)
|
257
273
|
access_token
|
258
274
|
else
|
259
275
|
# Extract any OAuth Problems reported in the response
|
260
276
|
oauth_data = Protocol.parse_authorization_header(response['WWW-Authenticate'])
|
261
277
|
# Raise an error for a failure to acquire a token
|
262
|
-
raise OAuthError.new('unable to acquire token', response.code, oauth_data[:oauth_problem])
|
278
|
+
raise OAuthError.new('unable to acquire token', response.code, oauth_data[:oauth_problem], nil, @realm)
|
263
279
|
end
|
264
280
|
end
|
265
281
|
|
@@ -278,10 +294,10 @@ module Cerner
|
|
278
294
|
when Net::HTTPSuccess
|
279
295
|
parsed_response = JSON.parse(response.body)
|
280
296
|
aes_key = parsed_response.dig('aesKey', 'secretKey')
|
281
|
-
raise OAuthError
|
297
|
+
raise OAuthError.new('AES secret key retrieved was invalid', nil, nil, nil, @realm) unless aes_key
|
282
298
|
|
283
299
|
rsa_key = parsed_response.dig('rsaKey', 'publicKey')
|
284
|
-
raise OAuthError
|
300
|
+
raise OAuthError.new('RSA public key retrieved was invalid', nil, nil, nil, @realm) unless rsa_key
|
285
301
|
|
286
302
|
Keys.new(
|
287
303
|
version: keys_version,
|
@@ -292,7 +308,7 @@ module Cerner
|
|
292
308
|
# Extract any OAuth Problems reported in the response
|
293
309
|
oauth_data = Protocol.parse_authorization_header(response['WWW-Authenticate'])
|
294
310
|
# Raise an error for a failure to acquire keys
|
295
|
-
raise OAuthError.new('unable to acquire keys', response.code, oauth_data[:oauth_problem])
|
311
|
+
raise OAuthError.new('unable to acquire keys', response.code, oauth_data[:oauth_problem], nil, @realm)
|
296
312
|
end
|
297
313
|
end
|
298
314
|
end
|
@@ -18,6 +18,9 @@ module Cerner
|
|
18
18
|
# May be nil.
|
19
19
|
attr_reader :oauth_parameters
|
20
20
|
|
21
|
+
# Returns a String with the Protection Realm associated with this error. May be nil.
|
22
|
+
attr_reader :realm
|
23
|
+
|
21
24
|
# Public: Construct an instance with a message, optional HTTP response code
|
22
25
|
# and optional OAuth Problem string.
|
23
26
|
#
|
@@ -27,30 +30,35 @@ module Cerner
|
|
27
30
|
# oauth_parameters - A String/Symbol or Array of Strings/Symbols containing the names of parameters that
|
28
31
|
# are absent or rejected. This is should only be used when oauth_problem
|
29
32
|
# is 'parameter_absent' or 'parameter_rejected' Optional.
|
33
|
+
# realm - The protection realm associated with the error. Optional.
|
30
34
|
def initialize(
|
31
35
|
message,
|
32
36
|
http_response_code = nil,
|
33
37
|
oauth_problem = nil,
|
34
|
-
oauth_parameters = nil
|
38
|
+
oauth_parameters = nil,
|
39
|
+
realm = nil
|
35
40
|
)
|
36
41
|
@http_response_code = http_response_code
|
37
42
|
@oauth_problem = oauth_problem
|
38
43
|
@oauth_parameters = oauth_parameters ? Array(oauth_parameters) : nil
|
44
|
+
@realm = realm
|
39
45
|
|
40
46
|
parts = []
|
41
47
|
parts << message if message
|
42
48
|
parts << "HTTP #{@http_response_code}" if @http_response_code
|
43
49
|
parts << "OAuth Problem #{@oauth_problem}" if @oauth_problem
|
44
50
|
parts << "OAuth Parameters [#{@oauth_parameters.join(', ')}]" if @oauth_parameters
|
51
|
+
parts << "OAuth Realm #{@realm}" if @realm
|
45
52
|
super(parts.empty? ? nil : parts.join(' '))
|
46
53
|
end
|
47
54
|
|
48
55
|
# Public: Generates an HTTP WWW-Authenticate header value based from the
|
49
56
|
# data in this OAuthError.
|
50
57
|
#
|
51
|
-
# Returns the generated value or nil if there is no #oauth_problem
|
58
|
+
# Returns the generated value or nil if there is no #oauth_problem or #realm.
|
52
59
|
def to_http_www_authenticate_header
|
53
60
|
params = {}
|
61
|
+
params[:realm] = @realm if @realm
|
54
62
|
params[:oauth_problem] = @oauth_problem if @oauth_problem
|
55
63
|
|
56
64
|
if @oauth_problem && @oauth_parameters
|
@@ -69,19 +69,22 @@ module Cerner
|
|
69
69
|
#
|
70
70
|
# params = { realm: 'https://test.host', oauth_problem: 'token_expired' }
|
71
71
|
# Cerner::OAuth1a::Protocol.generate_www_authenticate_header(params)
|
72
|
-
# # => "OAuth realm=\"https
|
72
|
+
# # => "OAuth realm=\"https://test.host\",oauth_problem=\"token_expired\""
|
73
73
|
#
|
74
74
|
# Returns the String containing the generated value or nil if params is nil or empty.
|
75
75
|
def self.generate_authorization_header(params)
|
76
76
|
return nil unless params && !params.empty?
|
77
77
|
|
78
|
+
realm = "realm=\"#{params.delete(:realm)}\"" if params[:realm]
|
79
|
+
realm += ', ' if realm && !params.empty?
|
80
|
+
|
78
81
|
encoded_params = params.map do |k, v|
|
79
82
|
k = URI.encode_www_form_component(k).gsub('+', '%20')
|
80
83
|
v = URI.encode_www_form_component(v).gsub('+', '%20')
|
81
84
|
"#{k}=\"#{v}\""
|
82
85
|
end
|
83
86
|
|
84
|
-
|
87
|
+
"OAuth #{realm}#{encoded_params.join(',')}"
|
85
88
|
end
|
86
89
|
|
87
90
|
# Alias the parse and generate methods
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cerner-oauth1a
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Beyer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-01
|
11
|
+
date: 2019-02-01 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
14
|
A minimal dependency library for interacting with a Cerner OAuth 1.0a Access
|