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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0dfb4c9bf4b9fa35f52b7fad741f860bde8fd792412bbd33e0715371019dd130
4
- data.tar.gz: ea584dbe2eb91a9965249c2f1330cdaa315fba73a4d5e6665e9fd27506ff1dd3
3
+ metadata.gz: cd0cf4f52bc3f90e247bc8c507415f540ababac1483da4c626294328857dc270
4
+ data.tar.gz: 2ae54c85c1ab06b6a3d9581cc062fae1a4a3857037131fbcb479812474f72756
5
5
  SHA512:
6
- metadata.gz: 9371797cb286811967f7e3af33428fc525e4b464bebd1ab550e99ed28daba6a1878eee561959a0469af9937bdb405591548aed1aba9b317137080554fad97f54
7
- data.tar.gz: 65398309db80afb19529f0d2d9988d4c1f061aedc6d6406b4f03eed5e9cae55e6da89605780d2f762532cb77a3964f5c1316868df10fa227f4c0e2c13b79cc3a
6
+ metadata.gz: 429eb27f3433283a68643968c83ab8ff0b0322bd751ac8da475bb62978922dd4e78d110e3561128b91010a8998070b06d286ebd2ccaa87f730f72943340d69e6
7
+ data.tar.gz: 3ada2c0200c845f5710a7eacbf77fbea95a3171a9ca56dc1270265a5d2511c7ee7de62fb58371f23f7eb960ce846ba3b762cce0a9d1292938280eae0177be872
@@ -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:
@@ -1,3 +1,3 @@
1
1
  * Cerner Corporation
2
2
  * Nathan Beyer [@nbeyer](https://github.com/nbeyer)
3
- * John leacox [@johnlcox](https://github.com/johnlcox)
3
+ * John Leacox [@johnlcox](https://github.com/johnlcox)
@@ -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('token references invalid keys version', nil, 'oauth_parameters_rejected', 'oauth_token')
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
- raise OAuthError.new('missing signature', nil, 'oauth_parameters_absent', 'oauth_signature') unless @signature
345
- raise OAuthError.new('missing HMACSecrets', nil, 'oauth_parameters_rejected', 'oauth_token') unless hmac_secrets
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
- raise OAuthError.new('signature is not valid', nil, 'signature_invalid') unless @signature == expected_signature
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, 'AES secret key retrieved was invalid' unless aes_key
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, 'RSA public key retrieved was invalid' unless rsa_key
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 and #oauth_parameters.
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%3A%2F%2Ftest.host\",oauth_problem=\"token_expired\""
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
- 'OAuth ' + encoded_params.join(',')
87
+ "OAuth #{realm}#{encoded_params.join(',')}"
85
88
  end
86
89
 
87
90
  # Alias the parse and generate methods
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cerner
4
4
  module OAuth1a
5
- VERSION = '2.0.1'
5
+ VERSION = '2.1.0'
6
6
  end
7
7
  end
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.1
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-31 00:00:00.000000000 Z
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