cerner-oauth1a 2.0.0.rc2 → 2.0.0.rc3
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/README.md +11 -0
- data/lib/cerner/oauth1a.rb +2 -0
- data/lib/cerner/oauth1a/access_token.rb +35 -5
- data/lib/cerner/oauth1a/access_token_agent.rb +11 -6
- data/lib/cerner/oauth1a/cache.rb +83 -29
- data/lib/cerner/oauth1a/cache_rails.rb +50 -0
- data/lib/cerner/oauth1a/protocol.rb +4 -0
- data/lib/cerner/oauth1a/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2a4ccfde185aca59fd7d639c4f15db74883c8932d38cc461b4ec1ad38a86e95
|
4
|
+
data.tar.gz: 051e4b68e33bdff8b347f5ec044e2d2a234455a49d6e9b1eef8487973c87fd3f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43460c5fe1996222164227d1fb8d98a93b7cedb16cf2dd6703a6e89324daab116a0c8994ebb13249aa8c5f8afa8fa5e708b8448ff0ef0d0d533924920d900688
|
7
|
+
data.tar.gz: 23fcbc356390d70b89dc1df139b6edb680e8077521b6b475916bc85f77312fd6b3e3777be9e30c2bc03bfcbe52179200198cefaada61a2c338335770d7935045
|
data/README.md
CHANGED
@@ -77,6 +77,17 @@ implement that:
|
|
77
77
|
# (xoauth_principal)
|
78
78
|
consumer_principal = access_token.consumer_principal
|
79
79
|
|
80
|
+
## Caching
|
81
|
+
|
82
|
+
The AccessTokenAgent class provides built-in memory caching. AccessTokens and Keys are cached
|
83
|
+
behind their respective retrieve methods. The caching can be disabled via parameters passed to the
|
84
|
+
constructor. See the class-level documentation for details.
|
85
|
+
|
86
|
+
### Caching in Rails
|
87
|
+
|
88
|
+
When the gem is loaded within a Rails application, it will attach a Railtie for initializing the
|
89
|
+
cache to use an implementation that stores the AccessTokens and Keys within Rails.cache.
|
90
|
+
|
80
91
|
## References
|
81
92
|
* https://wiki.ucern.com/display/public/reference/Cerner%27s+OAuth+Specification
|
82
93
|
* http://oauth.net/core/1.0a
|
data/lib/cerner/oauth1a.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'cerner/oauth1a/access_token'
|
4
4
|
require 'cerner/oauth1a/access_token_agent'
|
5
|
+
require 'cerner/oauth1a/cache'
|
6
|
+
require 'cerner/oauth1a/cache_rails' if defined?(::Rails) && defined?(::Rails.cache)
|
5
7
|
require 'cerner/oauth1a/keys'
|
6
8
|
require 'cerner/oauth1a/protocol'
|
7
9
|
require 'cerner/oauth1a/version'
|
@@ -211,6 +211,7 @@ module Cerner
|
|
211
211
|
def expired?(now: Time.now, fudge_sec: 300)
|
212
212
|
# if @expires_at is nil, return true now
|
213
213
|
return true unless @expires_at
|
214
|
+
|
214
215
|
now = convert_to_time(now)
|
215
216
|
now.tv_sec >= @expires_at.tv_sec - fudge_sec
|
216
217
|
end
|
@@ -254,7 +255,8 @@ module Cerner
|
|
254
255
|
token: @token,
|
255
256
|
token_secret: @token_secret,
|
256
257
|
signature_method: @signature_method,
|
257
|
-
signature: @signature
|
258
|
+
signature: @signature,
|
259
|
+
consumer_principal: @consumer_principal
|
258
260
|
}
|
259
261
|
end
|
260
262
|
|
@@ -267,6 +269,7 @@ module Cerner
|
|
267
269
|
# Returns a Time instance in the UTC time zone.
|
268
270
|
def convert_to_time(time)
|
269
271
|
raise ArgumentError, 'time is nil' unless time
|
272
|
+
|
270
273
|
if time.is_a? Time
|
271
274
|
time.utc
|
272
275
|
else
|
@@ -280,14 +283,36 @@ module Cerner
|
|
280
283
|
#
|
281
284
|
# Raises OAuthError if the parameter is invalid or expired
|
282
285
|
def verify_expiration(expires_on)
|
283
|
-
|
286
|
+
unless expires_on
|
287
|
+
raise OAuthError.new(
|
288
|
+
'token missing ExpiresOn',
|
289
|
+
nil,
|
290
|
+
'oauth_parameters_rejected',
|
291
|
+
'oauth_token'
|
292
|
+
)
|
293
|
+
end
|
294
|
+
|
284
295
|
expires_on = convert_to_time(expires_on)
|
285
296
|
now = convert_to_time(Time.now)
|
286
|
-
|
297
|
+
if now.tv_sec >= expires_on.tv_sec
|
298
|
+
raise OAuthError.new(
|
299
|
+
'token has expired',
|
300
|
+
nil,
|
301
|
+
'token_expired'
|
302
|
+
)
|
303
|
+
end
|
287
304
|
end
|
288
305
|
|
289
306
|
def load_keys(access_token_agent, keys_version)
|
290
|
-
|
307
|
+
unless keys_version
|
308
|
+
raise OAuthError.new(
|
309
|
+
'token missing KeysVersion',
|
310
|
+
nil,
|
311
|
+
'oauth_parameters_rejected',
|
312
|
+
'oauth_token'
|
313
|
+
)
|
314
|
+
end
|
315
|
+
|
291
316
|
begin
|
292
317
|
access_token_agent.retrieve_keys(keys_version)
|
293
318
|
rescue OAuthError
|
@@ -320,7 +345,12 @@ module Cerner
|
|
320
345
|
begin
|
321
346
|
secrets = keys.decrypt_hmac_secrets(hmac_secrets)
|
322
347
|
rescue ArgumentError, OpenSSL::PKey::RSAError => e
|
323
|
-
raise OAuthError.new(
|
348
|
+
raise OAuthError.new(
|
349
|
+
"unable to decrypt HMACSecrets: #{e.message}",
|
350
|
+
nil,
|
351
|
+
'oauth_parameters_rejected',
|
352
|
+
'oauth_token'
|
353
|
+
)
|
324
354
|
end
|
325
355
|
|
326
356
|
secrets_parts = Protocol.parse_url_query_string(secrets)
|
@@ -76,8 +76,8 @@ module Cerner
|
|
76
76
|
@open_timeout = (open_timeout ? open_timeout.to_i : 5)
|
77
77
|
@read_timeout = (read_timeout ? read_timeout.to_i : 5)
|
78
78
|
|
79
|
-
@keys_cache = cache_keys ? Cache.
|
80
|
-
@access_token_cache = cache_access_tokens ? Cache.
|
79
|
+
@keys_cache = cache_keys ? Cache.instance : nil
|
80
|
+
@access_token_cache = cache_access_tokens ? Cache.instance : nil
|
81
81
|
end
|
82
82
|
|
83
83
|
# Public: Retrieves the service provider keys from the configured Access Token service endpoint
|
@@ -96,14 +96,14 @@ module Cerner
|
|
96
96
|
raise ArgumentError, 'keys_version is nil' unless keys_version
|
97
97
|
|
98
98
|
if @keys_cache
|
99
|
-
cache_entry = @keys_cache.get(keys_version)
|
99
|
+
cache_entry = @keys_cache.get('cerner-oauth/keys', keys_version)
|
100
100
|
return cache_entry.value if cache_entry
|
101
101
|
end
|
102
102
|
|
103
103
|
request = retrieve_keys_prepare_request(keys_version)
|
104
104
|
response = http_client.request(request)
|
105
105
|
keys = retrieve_keys_handle_response(keys_version, response)
|
106
|
-
@keys_cache&.put(keys_version, Cache::KeysEntry.new(keys, Cache::TWENTY_FOUR_HOURS))
|
106
|
+
@keys_cache&.put('cerner-oauth/keys', keys_version, Cache::KeysEntry.new(keys, Cache::TWENTY_FOUR_HOURS))
|
107
107
|
keys
|
108
108
|
end
|
109
109
|
|
@@ -120,7 +120,7 @@ module Cerner
|
|
120
120
|
def retrieve(principal = nil)
|
121
121
|
cache_key = "#{@consumer_key}&#{principal}"
|
122
122
|
if @access_token_cache
|
123
|
-
cache_entry = @access_token_cache.get(cache_key)
|
123
|
+
cache_entry = @access_token_cache.get('cerner-oauth/access-tokens', cache_key)
|
124
124
|
return cache_entry.value if cache_entry
|
125
125
|
end
|
126
126
|
|
@@ -132,7 +132,7 @@ module Cerner
|
|
132
132
|
request = retrieve_prepare_request(timestamp, nonce, accessor_secret, principal)
|
133
133
|
response = http_client.request(request)
|
134
134
|
access_token = retrieve_handle_response(response, timestamp, nonce, accessor_secret)
|
135
|
-
@access_token_cache&.put(cache_key, Cache::AccessTokenEntry.new(access_token))
|
135
|
+
@access_token_cache&.put('cerner-oauth/access-tokens', cache_key, Cache::AccessTokenEntry.new(access_token))
|
136
136
|
access_token
|
137
137
|
end
|
138
138
|
|
@@ -191,6 +191,7 @@ module Cerner
|
|
191
191
|
# Raises ArgumentError if access_token_url is nil, invalid or not an HTTP/HTTPS URI
|
192
192
|
def convert_to_http_uri(access_token_url)
|
193
193
|
raise ArgumentError, 'access_token_url is nil' unless access_token_url
|
194
|
+
|
194
195
|
if access_token_url.is_a? URI
|
195
196
|
uri = access_token_url
|
196
197
|
else
|
@@ -201,7 +202,9 @@ module Cerner
|
|
201
202
|
raise ArgumentError, 'access_token_url is invalid'
|
202
203
|
end
|
203
204
|
end
|
205
|
+
|
204
206
|
raise ArgumentError, 'access_token_url must be an HTTP or HTTPS URI' unless uri.is_a?(URI::HTTP)
|
207
|
+
|
205
208
|
uri
|
206
209
|
end
|
207
210
|
|
@@ -269,8 +272,10 @@ module Cerner
|
|
269
272
|
parsed_response = JSON.parse(response.body)
|
270
273
|
aes_key = parsed_response.dig('aesKey', 'secretKey')
|
271
274
|
raise OAuthError, 'AES secret key retrieved was invalid' unless aes_key
|
275
|
+
|
272
276
|
rsa_key = parsed_response.dig('rsaKey', 'publicKey')
|
273
277
|
raise OAuthError, 'RSA public key retrieved was invalid' unless rsa_key
|
278
|
+
|
274
279
|
Keys.new(
|
275
280
|
version: keys_version,
|
276
281
|
aes_secret_key: Base64.decode64(aes_key),
|
data/lib/cerner/oauth1a/cache.rb
CHANGED
@@ -4,13 +4,33 @@ module Cerner
|
|
4
4
|
module OAuth1a
|
5
5
|
# Internal: A simple cache abstraction for use by AccessTokenAgent only.
|
6
6
|
class Cache
|
7
|
+
@cache_instance_lock = Mutex.new
|
8
|
+
|
9
|
+
def self.instance=(cache_impl)
|
10
|
+
raise ArgumentError, 'cache_impl must not be nil' unless cache_impl
|
11
|
+
|
12
|
+
@cache_instance_lock.synchronize do
|
13
|
+
@cache_instance = cache_impl
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.instance
|
18
|
+
@cache_instance_lock.synchronize do
|
19
|
+
return @cache_instance if @cache_instance
|
20
|
+
|
21
|
+
@cache_instance = DefaultCache.new(max: 50)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
7
25
|
# Internal: A cache entry class for Keys values.
|
8
26
|
class KeysEntry
|
9
27
|
attr_reader :value
|
28
|
+
attr_reader :expires_in
|
10
29
|
|
11
30
|
def initialize(keys, expires_in)
|
12
31
|
@value = keys
|
13
|
-
@
|
32
|
+
@expires_in = expires_in
|
33
|
+
@expires_at = Time.now.utc.to_i + @expires_in
|
14
34
|
end
|
15
35
|
|
16
36
|
def expired?(now)
|
@@ -26,6 +46,10 @@ module Cerner
|
|
26
46
|
@value = access_token
|
27
47
|
end
|
28
48
|
|
49
|
+
def expires_in
|
50
|
+
@value.expires_at.to_i - Time.now.utc.to_i
|
51
|
+
end
|
52
|
+
|
29
53
|
def expired?(now)
|
30
54
|
@value.expired?(now: now)
|
31
55
|
end
|
@@ -34,45 +58,75 @@ module Cerner
|
|
34
58
|
ONE_HOUR = 3600
|
35
59
|
TWENTY_FOUR_HOURS = 24 * ONE_HOUR
|
36
60
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
61
|
+
# Internal: The default implementation of the Cerner::OAuth1a::Cache interface.
|
62
|
+
# This implementation just maintains a capped list of entries in memory.
|
63
|
+
class DefaultCache < Cerner::OAuth1a::Cache
|
64
|
+
def initialize(max:)
|
65
|
+
super()
|
66
|
+
@max = max
|
67
|
+
@lock = Mutex.new
|
68
|
+
@entries = {}
|
69
|
+
end
|
42
70
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
71
|
+
def put(namespace, key, entry)
|
72
|
+
@lock.synchronize do
|
73
|
+
now = Time.now.utc.to_i
|
74
|
+
prune_expired(now)
|
75
|
+
@entries[full_key(namespace, key)] = entry
|
76
|
+
prune_size
|
77
|
+
end
|
49
78
|
end
|
50
|
-
end
|
51
79
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
80
|
+
def get(namespace, key)
|
81
|
+
@lock.synchronize do
|
82
|
+
prune_expired(Time.now.utc.to_i)
|
83
|
+
@entries[full_key(namespace, key)]
|
84
|
+
end
|
56
85
|
end
|
57
|
-
end
|
58
86
|
|
59
|
-
|
87
|
+
private
|
60
88
|
|
61
|
-
|
62
|
-
|
89
|
+
def prune_expired(now)
|
90
|
+
return if @entries.empty?
|
63
91
|
|
64
|
-
|
92
|
+
@entries.delete_if { |_, v| v.expired?(now) }
|
65
93
|
|
66
|
-
|
67
|
-
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def prune_size
|
98
|
+
return if @entries.empty? || @entries.size <= @max
|
68
99
|
|
69
|
-
|
70
|
-
|
100
|
+
num_to_prune = @entries.size - @max
|
101
|
+
num_to_prune.times { @entries.shift }
|
71
102
|
|
72
|
-
|
73
|
-
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
end
|
74
106
|
|
75
|
-
|
107
|
+
# Internal: The base constructor for the interface.
|
108
|
+
def initialize; end
|
109
|
+
|
110
|
+
# Internal: The abstract operation for putting (storing) data in the cache.
|
111
|
+
#
|
112
|
+
# namespace - The namespace for the cache entries.
|
113
|
+
# key - The key for the cache entries, which is qualified by namespace.
|
114
|
+
# entry - The entry to be stored in the cache.
|
115
|
+
def put(namespace, key, entry); end
|
116
|
+
|
117
|
+
# Internal: Retrieves the entry, if available, from the cache store.
|
118
|
+
#
|
119
|
+
# namespace - The namespace for the cache entries.
|
120
|
+
# key - The key for the cache entries.
|
121
|
+
def get(namespace, key); end
|
122
|
+
|
123
|
+
# Internal: Constructs a single, fully qualified key based on the namespace and key value
|
124
|
+
# passed.
|
125
|
+
#
|
126
|
+
# namespace - The namespace for the cache entries.
|
127
|
+
# key - The key for the cache entries.
|
128
|
+
def full_key(namespace, key)
|
129
|
+
"#{namespace}:#{key}"
|
76
130
|
end
|
77
131
|
end
|
78
132
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cerner
|
4
|
+
module OAuth1a
|
5
|
+
|
6
|
+
# Internal: A Railtie that initializer the cache implementation to use Rails.cache.
|
7
|
+
# This will be picked up automatically if ::Rails and ::Rails.cache are defined.
|
8
|
+
class CacheRailtie < ::Rails::Railtie
|
9
|
+
initializer 'cerner-oauth1a.cache_initialization' do |_app|
|
10
|
+
::Rails.logger.info "#{CacheRailtie.name}: configuring cache to use Rails.cache"
|
11
|
+
Cerner::OAuth1a::Cache.instance = RailsCache.new(::Rails.cache)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Internal: An implementation of the Cerner::OAuth1a::Cache interface that utilizes
|
16
|
+
# ::Rails.cache.
|
17
|
+
class RailsCache < Cerner::OAuth1a::Cache
|
18
|
+
# Internal: Constructs an instance with a instance of ActiveSupport::Cache::Store, which
|
19
|
+
# is generally ::Rails.cache.
|
20
|
+
#
|
21
|
+
# rails_cache - An instance of ActiveSupport::Cache::Store.
|
22
|
+
def initialize(rails_cache)
|
23
|
+
@cache = rails_cache
|
24
|
+
end
|
25
|
+
|
26
|
+
# Internal: Writes the entry to the cache store.
|
27
|
+
#
|
28
|
+
# namespace - The namespace for the cache entries.
|
29
|
+
# key - The key for the cache entries, which is qualified by namespace.
|
30
|
+
# entry - The entry to be stored in the cache.
|
31
|
+
def put(namespace, key, entry)
|
32
|
+
@cache.write(
|
33
|
+
key,
|
34
|
+
entry,
|
35
|
+
namespace: namespace,
|
36
|
+
expires_in: entry.expires_in,
|
37
|
+
race_condition_ttl: 5
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Internal: Retrieves the entry, if available, from the cache store.
|
42
|
+
#
|
43
|
+
# namespace - The namespace for the cache entries.
|
44
|
+
# key - The key for the cache entries.
|
45
|
+
def get(namespace, key)
|
46
|
+
@cache.read(key, namespace: namespace)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -122,9 +122,13 @@ module Cerner
|
|
122
122
|
# parameter.
|
123
123
|
def self.convert_problem_to_http_status(problem, default = :unauthorized)
|
124
124
|
return default unless problem
|
125
|
+
|
125
126
|
problem = problem.to_s
|
127
|
+
|
126
128
|
return :unauthorized if UNAUTHORIZED_PROBLEMS.include?(problem)
|
129
|
+
|
127
130
|
return :bad_request if BAD_REQUEST_PROBLEMS.include?(problem)
|
131
|
+
|
128
132
|
default
|
129
133
|
end
|
130
134
|
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.0.
|
4
|
+
version: 2.0.0.rc3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Beyer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-12-12 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
|
@@ -29,6 +29,7 @@ files:
|
|
29
29
|
- lib/cerner/oauth1a/access_token.rb
|
30
30
|
- lib/cerner/oauth1a/access_token_agent.rb
|
31
31
|
- lib/cerner/oauth1a/cache.rb
|
32
|
+
- lib/cerner/oauth1a/cache_rails.rb
|
32
33
|
- lib/cerner/oauth1a/keys.rb
|
33
34
|
- lib/cerner/oauth1a/oauth_error.rb
|
34
35
|
- lib/cerner/oauth1a/protocol.rb
|
@@ -53,7 +54,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
54
|
version: 1.3.1
|
54
55
|
requirements: []
|
55
56
|
rubyforge_project:
|
56
|
-
rubygems_version: 2.7.
|
57
|
+
rubygems_version: 2.7.7
|
57
58
|
signing_key:
|
58
59
|
specification_version: 4
|
59
60
|
summary: Cerner OAuth 1.0a Consumer and Service Provider Library.
|