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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64818192ce3e55c68d1dbf2a5cc77bf12225e5b5c80cbc3e121b5212f8e5adaa
4
- data.tar.gz: dfff2b1c96e60e9b55dbe5ddd024fb80bfbfe6e30613a88bf564cacfe38f1589
3
+ metadata.gz: d2a4ccfde185aca59fd7d639c4f15db74883c8932d38cc461b4ec1ad38a86e95
4
+ data.tar.gz: 051e4b68e33bdff8b347f5ec044e2d2a234455a49d6e9b1eef8487973c87fd3f
5
5
  SHA512:
6
- metadata.gz: 2482f15305e156725b2989609ec9b4c01d1b0535e1641215f34a769495f453043c2d376f405150ff31bc6fa1e94fe7d64f71ae65a8c619d9b06857b130ac01e7
7
- data.tar.gz: 9d5368c0f9662259fcd46ef8a3e53491406efc44a63580461be8b527f35df8578a6f23a7ef15b01ce08170581bccc0daee7d67524dda0f81aa58f9dec02a14bd
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
@@ -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
- raise OAuthError.new('token missing ExpiresOn', nil, 'oauth_parameters_rejected', 'oauth_token') unless expires_on
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
- raise OAuthError.new('token has expired', nil, 'token_expired') if now.tv_sec >= expires_on.tv_sec
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
- raise OAuthError.new('token missing KeysVersion', nil, 'oauth_parameters_rejected', 'oauth_token') unless keys_version
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("unable to decrypt HMACSecrets: #{e.message}", nil, 'oauth_parameters_rejected', 'oauth_token')
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.new(max: 10) : nil
80
- @access_token_cache = cache_access_tokens ? Cache.new(max: 5) : nil
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),
@@ -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
- @expires_at = Time.now.utc.to_i + expires_in
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
- def initialize(max:)
38
- @max = max
39
- @lock = Mutex.new
40
- @entries = {}
41
- end
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
- def put(key, entry)
44
- @lock.synchronize do
45
- now = Time.now.utc.to_i
46
- prune_expired(now)
47
- @entries[key] = entry
48
- prune_size
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
- def get(key)
53
- @lock.synchronize do
54
- prune_expired(Time.now.utc.to_i)
55
- @entries[key]
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
- private
87
+ private
60
88
 
61
- def prune_expired(now)
62
- return if @entries.empty?
89
+ def prune_expired(now)
90
+ return if @entries.empty?
63
91
 
64
- @entries.delete_if { |_, v| v.expired?(now) }
92
+ @entries.delete_if { |_, v| v.expired?(now) }
65
93
 
66
- nil
67
- end
94
+ nil
95
+ end
96
+
97
+ def prune_size
98
+ return if @entries.empty? || @entries.size <= @max
68
99
 
69
- def prune_size
70
- return if @entries.empty? || @entries.size <= @max
100
+ num_to_prune = @entries.size - @max
101
+ num_to_prune.times { @entries.shift }
71
102
 
72
- num_to_prune = @entries.size - @max
73
- num_to_prune.times { @entries.shift }
103
+ nil
104
+ end
105
+ end
74
106
 
75
- nil
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cerner
4
4
  module OAuth1a
5
- VERSION = '2.0.0.rc2'
5
+ VERSION = '2.0.0.rc3'
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.0.rc2
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-08-30 00:00:00.000000000 Z
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.6
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.