cerner-oauth1a 2.4.0 → 2.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/NOTICE +1 -1
- data/README.md +63 -1
- data/lib/cerner/oauth1a.rb +1 -0
- data/lib/cerner/oauth1a/access_token.rb +190 -86
- data/lib/cerner/oauth1a/access_token_agent.rb +66 -63
- data/lib/cerner/oauth1a/cache.rb +13 -5
- data/lib/cerner/oauth1a/cache_rails.rb +2 -7
- data/lib/cerner/oauth1a/internal.rb +95 -0
- data/lib/cerner/oauth1a/protocol.rb +32 -13
- data/lib/cerner/oauth1a/signature.rb +157 -0
- data/lib/cerner/oauth1a/version.rb +1 -1
- metadata +8 -6
@@ -5,10 +5,12 @@ require 'cerner/oauth1a/access_token'
|
|
5
5
|
require 'cerner/oauth1a/keys'
|
6
6
|
require 'cerner/oauth1a/oauth_error'
|
7
7
|
require 'cerner/oauth1a/cache'
|
8
|
+
require 'cerner/oauth1a/internal'
|
8
9
|
require 'cerner/oauth1a/protocol'
|
10
|
+
require 'cerner/oauth1a/signature'
|
9
11
|
require 'cerner/oauth1a/version'
|
10
12
|
require 'json'
|
11
|
-
require 'net/
|
13
|
+
require 'net/http'
|
12
14
|
require 'securerandom'
|
13
15
|
require 'uri'
|
14
16
|
|
@@ -70,9 +72,11 @@ module Cerner
|
|
70
72
|
# realm that's extracted from :access_token_url. If nil,
|
71
73
|
# this will be initalized with the DEFAULT_REALM_ALIASES.
|
72
74
|
# (optional, default: nil)
|
75
|
+
# :signature_method - A String to set the signature method to use. MUST be
|
76
|
+
# PLAINTEXT or HMAC-SHA1. (optional, default: 'PLAINTEXT')
|
73
77
|
#
|
74
78
|
# Raises ArgumentError if access_token_url, consumer_key or consumer_key is nil; if
|
75
|
-
# access_token_url is an invalid URI.
|
79
|
+
# access_token_url is an invalid URI; if signature_method is invalid.
|
76
80
|
def initialize(
|
77
81
|
access_token_url:,
|
78
82
|
consumer_key:,
|
@@ -81,7 +85,8 @@ module Cerner
|
|
81
85
|
read_timeout: 5,
|
82
86
|
cache_keys: true,
|
83
87
|
cache_access_tokens: true,
|
84
|
-
realm_aliases: nil
|
88
|
+
realm_aliases: nil,
|
89
|
+
signature_method: 'PLAINTEXT'
|
85
90
|
)
|
86
91
|
raise ArgumentError, 'consumer_key is nil' unless consumer_key
|
87
92
|
raise ArgumentError, 'consumer_secret is nil' unless consumer_secret
|
@@ -89,7 +94,7 @@ module Cerner
|
|
89
94
|
@consumer_key = consumer_key
|
90
95
|
@consumer_secret = consumer_secret
|
91
96
|
|
92
|
-
@access_token_url = convert_to_http_uri(access_token_url)
|
97
|
+
@access_token_url = Internal.convert_to_http_uri(url: access_token_url, name: 'access_token_url')
|
93
98
|
@realm = Protocol.realm_for(@access_token_url)
|
94
99
|
@realm_aliases = realm_aliases
|
95
100
|
@realm_aliases ||= DEFAULT_REALM_ALIASES[@realm]
|
@@ -99,6 +104,9 @@ module Cerner
|
|
99
104
|
|
100
105
|
@keys_cache = cache_keys ? Cache.instance : nil
|
101
106
|
@access_token_cache = cache_access_tokens ? Cache.instance : nil
|
107
|
+
|
108
|
+
@signature_method = signature_method || 'PLAINTEXT'
|
109
|
+
raise ArgumentError, 'signature_method is invalid' unless Signature::METHODS.include?(@signature_method)
|
102
110
|
end
|
103
111
|
|
104
112
|
# Public: Retrieves the service provider keys from the configured Access Token service endpoint
|
@@ -107,7 +115,7 @@ module Cerner
|
|
107
115
|
#
|
108
116
|
# keys_version - The version identifier of the keys to retrieve. This corresponds to the
|
109
117
|
# KeysVersion parameter of the oauth_token.
|
110
|
-
# keywords - The
|
118
|
+
# keywords - The keyword arguments:
|
111
119
|
# :ignore_cache - A flag for indicating that the cache should be ignored and a
|
112
120
|
# new Access Token should be retrieved.
|
113
121
|
#
|
@@ -135,7 +143,7 @@ module Cerner
|
|
135
143
|
# This method will use the #generate_accessor_secret, #generate_nonce and #generate_timestamp methods to
|
136
144
|
# interact with the service, which can be overridden via a sub-class, if desired.
|
137
145
|
#
|
138
|
-
# keywords - The
|
146
|
+
# keywords - The keyword arguments:
|
139
147
|
# :principal - An optional principal identifier, which is passed via the
|
140
148
|
# xoauth_principal protocol parameter.
|
141
149
|
# :ignore_cache - A flag for indicating that the cache should be ignored and a new
|
@@ -147,19 +155,20 @@ module Cerner
|
|
147
155
|
# Raises StandardError sub-classes for any issues interacting with the service, such as networking issues.
|
148
156
|
def retrieve(principal: nil, ignore_cache: false)
|
149
157
|
cache_key = "#{@consumer_key}&#{principal}"
|
158
|
+
|
150
159
|
if @access_token_cache && !ignore_cache
|
151
160
|
cache_entry = @access_token_cache.get('cerner-oauth1a/access-tokens', cache_key)
|
152
161
|
return cache_entry.value if cache_entry
|
153
162
|
end
|
154
163
|
|
155
164
|
# generate token request info
|
156
|
-
nonce = generate_nonce
|
157
165
|
timestamp = generate_timestamp
|
158
166
|
accessor_secret = generate_accessor_secret
|
159
167
|
|
160
|
-
request = retrieve_prepare_request(timestamp
|
168
|
+
request = retrieve_prepare_request(timestamp: timestamp, accessor_secret: accessor_secret, principal: principal)
|
161
169
|
response = http_client.request(request)
|
162
|
-
access_token =
|
170
|
+
access_token =
|
171
|
+
retrieve_handle_response(response: response, timestamp: timestamp, accessor_secret: accessor_secret)
|
163
172
|
@access_token_cache&.put('cerner-oauth1a/access-tokens', cache_key, Cache::AccessTokenEntry.new(access_token))
|
164
173
|
access_token
|
165
174
|
end
|
@@ -175,14 +184,14 @@ module Cerner
|
|
175
184
|
#
|
176
185
|
# Returns a String containing the nonce.
|
177
186
|
def generate_nonce
|
178
|
-
|
187
|
+
Internal.generate_nonce
|
179
188
|
end
|
180
189
|
|
181
190
|
# Public: Generate a Timestamp for invocations of the Access Token service.
|
182
191
|
#
|
183
192
|
# Returns an Integer representing the number of seconds since the epoch.
|
184
193
|
def generate_timestamp
|
185
|
-
|
194
|
+
Internal.generate_timestamp
|
186
195
|
end
|
187
196
|
|
188
197
|
# Public: Determines if the passed realm is equivalent to the configured
|
@@ -224,46 +233,41 @@ module Cerner
|
|
224
233
|
http
|
225
234
|
end
|
226
235
|
|
227
|
-
# Internal: Convert an Access Token URL into a URI with some verification checks
|
228
|
-
#
|
229
|
-
# access_token_url - A String URL or a URI instance
|
230
|
-
# Returns a URI::HTTP or URI::HTTPS
|
231
|
-
#
|
232
|
-
# Raises ArgumentError if access_token_url is nil, invalid or not an HTTP/HTTPS URI
|
233
|
-
def convert_to_http_uri(access_token_url)
|
234
|
-
raise ArgumentError, 'access_token_url is nil' unless access_token_url
|
235
|
-
|
236
|
-
if access_token_url.is_a?(URI)
|
237
|
-
uri = access_token_url
|
238
|
-
else
|
239
|
-
begin
|
240
|
-
uri = URI(access_token_url)
|
241
|
-
rescue URI::InvalidURIError
|
242
|
-
# raise argument error with cause
|
243
|
-
raise ArgumentError, 'access_token_url is invalid'
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
raise ArgumentError, 'access_token_url must be an HTTP or HTTPS URI' unless uri.is_a?(URI::HTTP)
|
248
|
-
|
249
|
-
uri
|
250
|
-
end
|
251
|
-
|
252
236
|
# Internal: Prepare a request for #retrieve
|
253
|
-
def retrieve_prepare_request(timestamp
|
237
|
+
def retrieve_prepare_request(accessor_secret:, timestamp:, principal: nil)
|
254
238
|
# construct a POST request
|
255
239
|
request = Net::HTTP::Post.new(@access_token_url)
|
256
240
|
# setup the data to construct the POST's message
|
257
|
-
params =
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
241
|
+
params = {
|
242
|
+
oauth_consumer_key: Protocol.percent_encode(@consumer_key),
|
243
|
+
oauth_signature_method: @signature_method,
|
244
|
+
oauth_version: '1.0',
|
245
|
+
oauth_accessor_secret: accessor_secret
|
246
|
+
}
|
247
|
+
params[:xoauth_principal] = principal.to_s if principal
|
248
|
+
|
249
|
+
if @signature_method == 'PLAINTEXT'
|
250
|
+
sig = Signature.sign_via_plaintext(client_shared_secret: @consumer_secret, token_shared_secret: '')
|
251
|
+
elsif @signature_method == 'HMAC-SHA1'
|
252
|
+
params[:oauth_timestamp] = timestamp
|
253
|
+
params[:oauth_nonce] = generate_nonce
|
254
|
+
signature_base_string =
|
255
|
+
Signature.build_signature_base_string(
|
256
|
+
http_method: 'POST', fully_qualified_url: @access_token_url, params: params
|
257
|
+
)
|
258
|
+
sig =
|
259
|
+
Signature.sign_via_hmacsha1(
|
260
|
+
client_shared_secret: @consumer_secret,
|
261
|
+
token_shared_secret: '',
|
262
|
+
signature_base_string: signature_base_string
|
263
|
+
)
|
264
|
+
else
|
265
|
+
raise OAuthError.new('signature_method is invalid', nil, 'signature_method_rejected', nil, @realm)
|
266
|
+
end
|
267
|
+
|
268
|
+
params[:oauth_signature] = sig
|
269
|
+
|
270
|
+
params = params.map { |n, v| [n, v] }
|
267
271
|
# set the POST's body as a URL form-encoded string
|
268
272
|
request.set_form(params, MIME_WWW_FORM_URL_ENCODED, charset: 'UTF-8')
|
269
273
|
request['Accept'] = MIME_WWW_FORM_URL_ENCODED
|
@@ -273,22 +277,22 @@ module Cerner
|
|
273
277
|
end
|
274
278
|
|
275
279
|
# Internal: Handle a response for #retrieve
|
276
|
-
def retrieve_handle_response(response
|
280
|
+
def retrieve_handle_response(response:, timestamp:, accessor_secret:)
|
277
281
|
case response
|
278
282
|
when Net::HTTPSuccess
|
279
283
|
# Parse the HTTP response and convert it into a Symbol-keyed Hash
|
280
284
|
tuples = Protocol.parse_url_query_string(response.body)
|
281
285
|
# Use the parsed response to construct the AccessToken
|
282
|
-
access_token =
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
286
|
+
access_token =
|
287
|
+
AccessToken.new(
|
288
|
+
accessor_secret: accessor_secret,
|
289
|
+
consumer_key: @consumer_key,
|
290
|
+
expires_at: timestamp + tuples[:oauth_expires_in].to_i,
|
291
|
+
token: tuples[:oauth_token],
|
292
|
+
token_secret: tuples[:oauth_token_secret],
|
293
|
+
signature_method: @signature_method,
|
294
|
+
realm: @realm
|
295
|
+
)
|
292
296
|
access_token
|
293
297
|
else
|
294
298
|
# Extract any OAuth Problems reported in the response
|
@@ -300,10 +304,11 @@ module Cerner
|
|
300
304
|
|
301
305
|
# Internal: Prepare a request for #retrieve_keys
|
302
306
|
def retrieve_keys_prepare_request(keys_version)
|
303
|
-
|
307
|
+
keys_url = URI("#{@access_token_url}/keys/#{keys_version}")
|
308
|
+
request = Net::HTTP::Get.new(keys_url)
|
304
309
|
request['Accept'] = 'application/json'
|
305
310
|
request['User-Agent'] = user_agent_string
|
306
|
-
request['Authorization'] = retrieve.authorization_header
|
311
|
+
request['Authorization'] = retrieve.authorization_header(fully_qualified_url: keys_url)
|
307
312
|
request
|
308
313
|
end
|
309
314
|
|
@@ -319,9 +324,7 @@ module Cerner
|
|
319
324
|
raise OAuthError.new('RSA public key retrieved was invalid', nil, nil, nil, @realm) unless rsa_key
|
320
325
|
|
321
326
|
Keys.new(
|
322
|
-
version: keys_version,
|
323
|
-
aes_secret_key: Base64.decode64(aes_key),
|
324
|
-
rsa_public_key: Base64.decode64(rsa_key)
|
327
|
+
version: keys_version, aes_secret_key: Base64.decode64(aes_key), rsa_public_key: Base64.decode64(rsa_key)
|
325
328
|
)
|
326
329
|
else
|
327
330
|
# Extract any OAuth Problems reported in the response
|
data/lib/cerner/oauth1a/cache.rb
CHANGED
@@ -6,17 +6,17 @@ module Cerner
|
|
6
6
|
class Cache
|
7
7
|
@cache_instance_lock = Mutex.new
|
8
8
|
|
9
|
+
# Internal: Sets the singleton instance.
|
9
10
|
def self.instance=(cache_impl)
|
10
11
|
raise ArgumentError, 'cache_impl must not be nil' unless cache_impl
|
11
12
|
|
12
|
-
@cache_instance_lock.synchronize
|
13
|
-
@cache_instance = cache_impl
|
14
|
-
end
|
13
|
+
@cache_instance_lock.synchronize { @cache_instance = cache_impl }
|
15
14
|
end
|
16
15
|
|
16
|
+
# Internal: Gets the singleton instance.
|
17
17
|
def self.instance
|
18
18
|
@cache_instance_lock.synchronize do
|
19
|
-
return @cache_instance if @cache_instance
|
19
|
+
return @cache_instance if instance_variable_defined?(:@cache_instance) && @cache_instance
|
20
20
|
|
21
21
|
@cache_instance = DefaultCache.new(max: 50)
|
22
22
|
end
|
@@ -27,12 +27,14 @@ module Cerner
|
|
27
27
|
attr_reader :value
|
28
28
|
attr_reader :expires_in
|
29
29
|
|
30
|
+
# Internal: Constructs an instance.
|
30
31
|
def initialize(keys, expires_in)
|
31
32
|
@value = keys
|
32
33
|
@expires_in = expires_in
|
33
34
|
@expires_at = Time.now.utc.to_i + @expires_in
|
34
35
|
end
|
35
36
|
|
37
|
+
# Internal: Check if the entry is expired.
|
36
38
|
def expired?(now)
|
37
39
|
@expires_at <= now
|
38
40
|
end
|
@@ -42,25 +44,29 @@ module Cerner
|
|
42
44
|
class AccessTokenEntry
|
43
45
|
attr_reader :value
|
44
46
|
|
47
|
+
# Internal: Constructs an instance.
|
45
48
|
def initialize(access_token)
|
46
49
|
@value = access_token
|
47
50
|
end
|
48
51
|
|
52
|
+
# Internal: Returns the number of seconds until the entry expires.
|
49
53
|
def expires_in
|
50
54
|
@value.expires_at.to_i - Time.now.utc.to_i
|
51
55
|
end
|
52
56
|
|
57
|
+
# Internal: Check if the entry is expired.
|
53
58
|
def expired?(now)
|
54
59
|
@value.expired?(now: now)
|
55
60
|
end
|
56
61
|
end
|
57
62
|
|
58
|
-
ONE_HOUR =
|
63
|
+
ONE_HOUR = 3_600
|
59
64
|
TWENTY_FOUR_HOURS = 24 * ONE_HOUR
|
60
65
|
|
61
66
|
# Internal: The default implementation of the Cerner::OAuth1a::Cache interface.
|
62
67
|
# This implementation just maintains a capped list of entries in memory.
|
63
68
|
class DefaultCache < Cerner::OAuth1a::Cache
|
69
|
+
# Internal: Constructs an instance.
|
64
70
|
def initialize(max:)
|
65
71
|
super()
|
66
72
|
@max = max
|
@@ -68,6 +74,7 @@ module Cerner
|
|
68
74
|
@entries = {}
|
69
75
|
end
|
70
76
|
|
77
|
+
# Internal: Puts an entry into the cache.
|
71
78
|
def put(namespace, key, entry)
|
72
79
|
@lock.synchronize do
|
73
80
|
now = Time.now.utc.to_i
|
@@ -77,6 +84,7 @@ module Cerner
|
|
77
84
|
end
|
78
85
|
end
|
79
86
|
|
87
|
+
# Internal: Gets an entry from the cache.
|
80
88
|
def get(namespace, key)
|
81
89
|
@lock.synchronize do
|
82
90
|
prune_expired(Time.now.utc.to_i)
|
@@ -19,6 +19,7 @@ module Cerner
|
|
19
19
|
#
|
20
20
|
# rails_cache - An instance of ActiveSupport::Cache::Store.
|
21
21
|
def initialize(rails_cache)
|
22
|
+
super()
|
22
23
|
@cache = rails_cache
|
23
24
|
end
|
24
25
|
|
@@ -28,13 +29,7 @@ module Cerner
|
|
28
29
|
# key - The key for the cache entries, which is qualified by namespace.
|
29
30
|
# entry - The entry to be stored in the cache.
|
30
31
|
def put(namespace, key, entry)
|
31
|
-
@cache.write(
|
32
|
-
key,
|
33
|
-
entry,
|
34
|
-
namespace: namespace,
|
35
|
-
expires_in: entry.expires_in,
|
36
|
-
race_condition_ttl: 5
|
37
|
-
)
|
32
|
+
@cache.write(key, entry, namespace: namespace, expires_in: entry.expires_in, race_condition_ttl: 5)
|
38
33
|
end
|
39
34
|
|
40
35
|
# Internal: Retrieves the entry, if available, from the cache store.
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Cerner
|
7
|
+
module OAuth1a
|
8
|
+
# Internal: Internal utility methods
|
9
|
+
module Internal
|
10
|
+
# Internal: Convert a time value into a Time instance.
|
11
|
+
#
|
12
|
+
# keywords - The keyword arguments:
|
13
|
+
# :time - Time or any object with a #to_i that returns an Integer.
|
14
|
+
# :name - The parameter name of the data for invoking methods.
|
15
|
+
#
|
16
|
+
# Returns a Time instance in the UTC time zone.
|
17
|
+
def self.convert_to_time(time:, name: 'time')
|
18
|
+
raise ArgumentError, "#{name} is nil" unless time
|
19
|
+
|
20
|
+
time.is_a?(Time) ? time.utc : Time.at(time.to_i).utc
|
21
|
+
end
|
22
|
+
|
23
|
+
# Internal: Convert an fully qualified URL String into a URI with some verification checks
|
24
|
+
#
|
25
|
+
# keywords - The keyword arguments:
|
26
|
+
# :url - A String or a URI instance to convert to a URI instance.
|
27
|
+
# :name - The parameter name of the URL for invoking methods.
|
28
|
+
#
|
29
|
+
# Returns a URI::HTTP or URI::HTTPS
|
30
|
+
#
|
31
|
+
# Raises ArgumentError if url is nil, invalid or not an HTTP/HTTPS URI
|
32
|
+
def self.convert_to_http_uri(url:, name: 'url')
|
33
|
+
raise ArgumentError, "#{name} is nil" unless url
|
34
|
+
|
35
|
+
if url.is_a?(URI)
|
36
|
+
uri = url
|
37
|
+
else
|
38
|
+
begin
|
39
|
+
uri = URI(url)
|
40
|
+
rescue URI::InvalidURIError
|
41
|
+
# raise argument error with cause
|
42
|
+
raise ArgumentError, "#{name} is invalid"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
raise ArgumentError, "#{name} must be an HTTP or HTTPS URI" unless uri.is_a?(URI::HTTP)
|
47
|
+
|
48
|
+
uri
|
49
|
+
end
|
50
|
+
|
51
|
+
# Internal: Generate a Nonce for invocations of the Access Token service.
|
52
|
+
#
|
53
|
+
# Returns a String containing the nonce.
|
54
|
+
def self.generate_nonce
|
55
|
+
SecureRandom.hex
|
56
|
+
end
|
57
|
+
|
58
|
+
# Internal: Generate a Timestamp for invocations of the Access Token service.
|
59
|
+
#
|
60
|
+
# Returns an Integer representing the number of seconds since the epoch.
|
61
|
+
def self.generate_timestamp
|
62
|
+
Time.now.to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
# Internal: Compares two Strings using a constant time algorithm to protect against timing
|
66
|
+
# attacks.
|
67
|
+
#
|
68
|
+
# left - The left String
|
69
|
+
# right - The right String
|
70
|
+
#
|
71
|
+
# Return true if left and right match, false otherwise.
|
72
|
+
def self.constant_time_compare(left, right)
|
73
|
+
max_size = [left.bytesize, right.bytesize].max
|
74
|
+
# convert left and right to array of bytes (Integer)
|
75
|
+
left = left.unpack('C*')
|
76
|
+
right = right.unpack('C*')
|
77
|
+
|
78
|
+
# if either array is not the max size, expand it with zeros
|
79
|
+
# having equal arrays keeps the algorithm execution time constant
|
80
|
+
left = left.fill(0, left.size, max_size - left.size) if left.size < max_size
|
81
|
+
right = right.fill(0, right.size, max_size - right.size) if right.size < max_size
|
82
|
+
|
83
|
+
result = 0
|
84
|
+
left.each_with_index do |left_value, i|
|
85
|
+
# XOR the two bytes, if equal, the operation is 0
|
86
|
+
# OR the XOR operation with the previous result
|
87
|
+
result |= left_value ^ right[i]
|
88
|
+
end
|
89
|
+
|
90
|
+
# if every comparison resuled in 0, then left and right are equal
|
91
|
+
result.zero?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -6,6 +6,18 @@ module Cerner
|
|
6
6
|
module OAuth1a
|
7
7
|
# Public: OAuth 1.0a protocol utilities.
|
8
8
|
module Protocol
|
9
|
+
# Public: Encodes the passed text using the percent encoding variant described in the OAuth
|
10
|
+
# 1.0a specification.
|
11
|
+
#
|
12
|
+
# Reference: https://tools.ietf.org/html/rfc5849#section-3.6
|
13
|
+
#
|
14
|
+
# text - A String containing the text to encode.
|
15
|
+
#
|
16
|
+
# Returns a String that has been encoded.
|
17
|
+
def self.percent_encode(text)
|
18
|
+
URI.encode_www_form_component(text).gsub('+', '%20')
|
19
|
+
end
|
20
|
+
|
9
21
|
# Public: Parses a URL-encoded query string into a Hash with symbolized keys.
|
10
22
|
#
|
11
23
|
# query - String containing a URL-encoded query string to parse.
|
@@ -37,6 +49,10 @@ module Cerner
|
|
37
49
|
# Cerner::OAuth1a::Protocol.parse_www_authenticate_header(header)
|
38
50
|
# # => {:realm=>"https://test.host", :oauth_problem=>"token_expired"}
|
39
51
|
#
|
52
|
+
# header = 'OAuth realm="https://test.host", oauth_problem=token_expired'
|
53
|
+
# Cerner::OAuth1a::Protocol.parse_www_authenticate_header(header)
|
54
|
+
# # => {:realm=>"https://test.host", :oauth_problem=>"token_expired"}
|
55
|
+
#
|
40
56
|
# Returns a Hash with symbolized keys of all of the parameters.
|
41
57
|
def self.parse_authorization_header(value)
|
42
58
|
params = {}
|
@@ -45,10 +61,18 @@ module Cerner
|
|
45
61
|
value = value.strip
|
46
62
|
return params unless value.size > 6 && value[0..5].casecmp?('OAuth ')
|
47
63
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
64
|
+
# trim off 'OAuth ' prefix
|
65
|
+
value = value[6..-1]
|
66
|
+
|
67
|
+
# split value on comma separators
|
68
|
+
value.split(/,\s*/).each do |kv_part|
|
69
|
+
# split each part on '=' separator
|
70
|
+
key, value = kv_part.split('=')
|
71
|
+
key = URI.decode_www_form_component(key)
|
72
|
+
# trim off surrounding double quotes, if they exist
|
73
|
+
value = value[1..-2] if value.start_with?('"') && value.end_with?('"')
|
74
|
+
value = URI.decode_www_form_component(value)
|
75
|
+
params[key.to_sym] = value
|
52
76
|
end
|
53
77
|
|
54
78
|
params
|
@@ -73,17 +97,12 @@ module Cerner
|
|
73
97
|
#
|
74
98
|
# Returns the String containing the generated value or nil if params is nil or empty.
|
75
99
|
def self.generate_authorization_header(params)
|
76
|
-
return
|
100
|
+
return unless params && !params.empty?
|
77
101
|
|
78
102
|
realm = "realm=\"#{params.delete(:realm)}\"" if params[:realm]
|
79
|
-
realm += ',
|
80
|
-
|
81
|
-
encoded_params =
|
82
|
-
params.map do |k, v|
|
83
|
-
k = URI.encode_www_form_component(k).gsub('+', '%20')
|
84
|
-
v = URI.encode_www_form_component(v).gsub('+', '%20')
|
85
|
-
"#{k}=\"#{v}\""
|
86
|
-
end
|
103
|
+
realm += ',' if realm && !params.empty?
|
104
|
+
|
105
|
+
encoded_params = params.map { |k, v| "#{percent_encode(k)}=\"#{percent_encode(v)}\"" }
|
87
106
|
|
88
107
|
"OAuth #{realm}#{encoded_params.join(',')}"
|
89
108
|
end
|