cerner-oauth1a 2.0.0.rc2 → 2.0.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|