cerner-oauth1a 2.0.1 → 2.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/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
|