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 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.