cerner-oauth1a 1.0.1 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +9 -0
- data/NOTICE +1 -1
- data/README.md +77 -23
- data/lib/cerner/oauth1a.rb +4 -0
- data/lib/cerner/oauth1a/access_token.rb +224 -41
- data/lib/cerner/oauth1a/access_token_agent.rb +194 -87
- data/lib/cerner/oauth1a/cache.rb +79 -0
- data/lib/cerner/oauth1a/keys.rb +124 -0
- data/lib/cerner/oauth1a/oauth_error.rb +72 -6
- data/lib/cerner/oauth1a/protocol.rb +132 -0
- data/lib/cerner/oauth1a/version.rb +3 -1
- metadata +14 -9
@@ -1,15 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
1
4
|
require 'cerner/oauth1a/access_token'
|
5
|
+
require 'cerner/oauth1a/keys'
|
2
6
|
require 'cerner/oauth1a/oauth_error'
|
7
|
+
require 'cerner/oauth1a/cache'
|
8
|
+
require 'cerner/oauth1a/protocol'
|
3
9
|
require 'cerner/oauth1a/version'
|
10
|
+
require 'json'
|
4
11
|
require 'net/https'
|
5
12
|
require 'securerandom'
|
6
13
|
require 'uri'
|
7
14
|
|
8
15
|
module Cerner
|
9
16
|
module OAuth1a
|
10
|
-
|
11
|
-
#
|
12
|
-
# Access Tokens.
|
17
|
+
# Public: A user agent for interacting with the Cerner OAuth 1.0a Access Token service to acquire
|
18
|
+
# consumer Access Tokens or service provider Keys.
|
13
19
|
class AccessTokenAgent
|
14
20
|
MIME_WWW_FORM_URL_ENCODED = 'application/x-www-form-urlencoded'
|
15
21
|
|
@@ -22,18 +28,41 @@ module Cerner
|
|
22
28
|
|
23
29
|
# Public: Constructs an instance of the agent.
|
24
30
|
#
|
31
|
+
# Caching - By default, AccessToken and Keys instances are maintained in a small, constrained
|
32
|
+
# memory cache used by #retrieve and #retrieve_keys, respectively.
|
33
|
+
#
|
34
|
+
# The AccessToken cache keeps a maximum of 5 entries and prunes them when they expire. As the
|
35
|
+
# cache is based on the #consumer_key and the 'principal' parameter, the cache has limited
|
36
|
+
# effect. It's strongly suggested that AccessToken's be cached independently, as well.
|
37
|
+
#
|
38
|
+
# The Keys cache keeps a maximum of 10 entries and prunes them 24 hours after retrieval.
|
39
|
+
#
|
25
40
|
# arguments - The keyword arguments of the method:
|
26
|
-
# :access_token_url
|
27
|
-
# :consumer_key
|
28
|
-
# :consumer_secret
|
29
|
-
# :open_timeout
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
|
41
|
+
# :access_token_url - The String or URI of the Access Token service endpoint.
|
42
|
+
# :consumer_key - The String of the Consumer Key of the account.
|
43
|
+
# :consumer_secret - The String of the Consumer Secret of the account.
|
44
|
+
# :open_timeout - An object responding to to_i. Used to set the timeout, in
|
45
|
+
# seconds, for opening HTTP connections to the Access Token
|
46
|
+
# service (optional, default: 5).
|
47
|
+
# :read_timeout - An object responding to to_i. Used to set the timeout, in
|
48
|
+
# seconds, for reading data from HTTP connections to the
|
49
|
+
# Access Token service (optional, default: 5).
|
50
|
+
# :cache_keys - A Boolean for configuring Keys caching within
|
51
|
+
# #retrieve_keys. (optional, default: true)
|
52
|
+
# :cache_access_tokens - A Boolean for configuring AccessToken caching within
|
53
|
+
# #retrieve. (optional, default: true)
|
54
|
+
#
|
55
|
+
# Raises ArgumentError if access_token_url, consumer_key or consumer_key is nil; if
|
56
|
+
# access_token_url is an invalid URI.
|
57
|
+
def initialize(
|
58
|
+
access_token_url:,
|
59
|
+
consumer_key:,
|
60
|
+
consumer_secret:,
|
61
|
+
open_timeout: 5,
|
62
|
+
read_timeout: 5,
|
63
|
+
cache_keys: true,
|
64
|
+
cache_access_tokens: true
|
65
|
+
)
|
37
66
|
raise ArgumentError, 'consumer_key is nil' unless consumer_key
|
38
67
|
raise ArgumentError, 'consumer_secret is nil' unless consumer_secret
|
39
68
|
|
@@ -44,73 +73,65 @@ module Cerner
|
|
44
73
|
|
45
74
|
@open_timeout = (open_timeout ? open_timeout.to_i : 5)
|
46
75
|
@read_timeout = (read_timeout ? read_timeout.to_i : 5)
|
76
|
+
|
77
|
+
@keys_cache = cache_keys ? Cache.new(max: 10) : nil
|
78
|
+
@access_token_cache = cache_access_tokens ? Cache.new(max: 5) : nil
|
47
79
|
end
|
48
80
|
|
49
|
-
# Public:
|
50
|
-
# This method will
|
51
|
-
#
|
81
|
+
# Public: Retrieves the service provider keys from the configured Access Token service endpoint
|
82
|
+
# (@access_token_url). This method will invoke #retrieve to acquire an AccessToken to request
|
83
|
+
# the keys.
|
52
84
|
#
|
53
|
-
#
|
85
|
+
# keys_version - The version identifier of the keys to retrieve. This corresponds to the
|
86
|
+
# KeysVersion parameter of the oauth_token.
|
54
87
|
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
|
58
|
-
|
59
|
-
|
88
|
+
# Return a Keys instance upon success.
|
89
|
+
#
|
90
|
+
# Raises ArgumentError if keys_version is nil.
|
91
|
+
# Raises OAuthError for any functional errors returned within an HTTP 200 response.
|
92
|
+
# Raises StandardError sub-classes for any issues interacting with the service, such as networking issues.
|
93
|
+
def retrieve_keys(keys_version)
|
94
|
+
raise ArgumentError, 'keys_version is nil' unless keys_version
|
60
95
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
params = [
|
66
|
-
[:oauth_consumer_key, @consumer_key],
|
67
|
-
[:oauth_signature_method, 'PLAINTEXT'],
|
68
|
-
[:oauth_version, '1.0'],
|
69
|
-
[:oauth_timestamp, timestamp],
|
70
|
-
[:oauth_nonce, nonce],
|
71
|
-
[:oauth_signature, "#{@consumer_secret}&"],
|
72
|
-
[:oauth_accessor_secret, accessor_secret]
|
73
|
-
]
|
74
|
-
# set the POST's body as a URL form-encoded string
|
75
|
-
request.set_form(params, MIME_WWW_FORM_URL_ENCODED, charset: 'UTF-8')
|
96
|
+
if @keys_cache
|
97
|
+
cache_entry = @keys_cache.get(keys_version)
|
98
|
+
return cache_entry.value if cache_entry
|
99
|
+
end
|
76
100
|
|
77
|
-
request
|
78
|
-
|
79
|
-
|
101
|
+
request = retrieve_keys_prepare_request(keys_version)
|
102
|
+
response = http_client.request(request)
|
103
|
+
keys = retrieve_keys_handle_response(keys_version, response)
|
104
|
+
@keys_cache&.put(keys_version, Cache::KeysEntry.new(keys, Cache::TWENTY_FOUR_HOURS))
|
105
|
+
keys
|
106
|
+
end
|
80
107
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
108
|
+
# Public: Retrieves an AccessToken from the configured Access Token service endpoint (#access_token_url).
|
109
|
+
# This method will use the #generate_accessor_secret, #generate_nonce and #generate_timestamp methods to
|
110
|
+
# interact with the service, which can be overridden via a sub-class, if desired.
|
111
|
+
#
|
112
|
+
# principal - An optional principal identifier, which is passed via the xoauth_principal protocol parameter.
|
113
|
+
#
|
114
|
+
# Returns a AccessToken upon success.
|
115
|
+
#
|
116
|
+
# Raises OAuthError for any functional errors returned within an HTTP 200 response.
|
117
|
+
# Raises StandardError sub-classes for any issues interacting with the service, such as networking issues.
|
118
|
+
def retrieve(principal = nil)
|
119
|
+
cache_key = "#{@consumer_key}&#{principal}"
|
120
|
+
if @access_token_cache
|
121
|
+
cache_entry = @access_token_cache.get(cache_key)
|
122
|
+
return cache_entry.value if cache_entry
|
89
123
|
end
|
90
|
-
http.open_timeout = @open_timeout
|
91
|
-
http.read_timeout = @read_timeout
|
92
124
|
|
93
|
-
|
125
|
+
# generate token request info
|
126
|
+
nonce = generate_nonce
|
127
|
+
timestamp = generate_timestamp
|
128
|
+
accessor_secret = generate_accessor_secret
|
94
129
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
access_token = AccessToken.new(accessor_secret: accessor_secret,
|
101
|
-
consumer_key: @consumer_key,
|
102
|
-
expires_at: timestamp + tuples[:oauth_expires_in].to_i,
|
103
|
-
nonce: nonce,
|
104
|
-
timestamp: timestamp,
|
105
|
-
token: tuples[:oauth_token],
|
106
|
-
token_secret: tuples[:oauth_token_secret])
|
107
|
-
access_token
|
108
|
-
else
|
109
|
-
# Extract any OAuth Problems reported in the response
|
110
|
-
oauth_data = parse_www_authenticate(response['WWW-Authenticate'])
|
111
|
-
# Raise an error for a failure to acquire a token
|
112
|
-
raise OAuthError.new('unable to acquire token', response.code, oauth_data['oauth_problem'])
|
113
|
-
end
|
130
|
+
request = retrieve_prepare_request(timestamp, nonce, accessor_secret, principal)
|
131
|
+
response = http_client.request(request)
|
132
|
+
access_token = retrieve_handle_response(response, timestamp, nonce, accessor_secret)
|
133
|
+
@access_token_cache&.put(cache_key, Cache::AccessTokenEntry.new(access_token))
|
134
|
+
access_token
|
114
135
|
end
|
115
136
|
|
116
137
|
# Public: Generate an Accessor Secret for invocations of the Access Token service.
|
@@ -136,18 +157,28 @@ module Cerner
|
|
136
157
|
|
137
158
|
private
|
138
159
|
|
139
|
-
# Internal:
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
#
|
145
|
-
def
|
146
|
-
|
147
|
-
|
148
|
-
|
160
|
+
# Internal: Generate a User-Agent HTTP Header string
|
161
|
+
def user_agent_string
|
162
|
+
"cerner-oauth1a #{VERSION} (Ruby #{RUBY_VERSION})"
|
163
|
+
end
|
164
|
+
|
165
|
+
# Internal: Provide the HTTP client instance for invoking requests
|
166
|
+
def http_client
|
167
|
+
http = Net::HTTP.new(@access_token_url.host, @access_token_url.port)
|
168
|
+
|
169
|
+
if @access_token_url.scheme == 'https'
|
170
|
+
# if the scheme is HTTPS, then enable SSL
|
171
|
+
http.use_ssl = true
|
172
|
+
# make sure to verify peers
|
173
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
174
|
+
# tweak the ciphers to eliminate unsafe options
|
175
|
+
http.ciphers = 'DEFAULT:!aNULL:!eNULL:!LOW:!SSLv2:!RC4'
|
176
|
+
end
|
177
|
+
|
178
|
+
http.open_timeout = @open_timeout
|
179
|
+
http.read_timeout = @read_timeout
|
149
180
|
|
150
|
-
|
181
|
+
http
|
151
182
|
end
|
152
183
|
|
153
184
|
# Internal: Convert an Access Token URL into a URI with some verification checks
|
@@ -163,17 +194,93 @@ module Cerner
|
|
163
194
|
else
|
164
195
|
begin
|
165
196
|
uri = URI(access_token_url)
|
166
|
-
rescue URI::InvalidURIError
|
197
|
+
rescue URI::InvalidURIError
|
167
198
|
# raise argument error with cause
|
168
199
|
raise ArgumentError, 'access_token_url is invalid'
|
169
200
|
end
|
170
201
|
end
|
171
|
-
unless uri.is_a?
|
172
|
-
raise ArgumentError, 'access_token_url must be an HTTP or HTTPS URI'
|
173
|
-
end
|
202
|
+
raise ArgumentError, 'access_token_url must be an HTTP or HTTPS URI' unless uri.is_a?(URI::HTTP)
|
174
203
|
uri
|
175
204
|
end
|
176
|
-
end
|
177
205
|
|
206
|
+
# Internal: Prepare a request for #retrieve
|
207
|
+
def retrieve_prepare_request(timestamp, nonce, accessor_secret, principal)
|
208
|
+
# construct a POST request
|
209
|
+
request = Net::HTTP::Post.new(@access_token_url)
|
210
|
+
# setup the data to construct the POST's message
|
211
|
+
params = [
|
212
|
+
[:oauth_consumer_key, @consumer_key],
|
213
|
+
[:oauth_signature_method, 'PLAINTEXT'],
|
214
|
+
[:oauth_version, '1.0'],
|
215
|
+
[:oauth_timestamp, timestamp],
|
216
|
+
[:oauth_nonce, nonce],
|
217
|
+
[:oauth_signature, "#{@consumer_secret}&"],
|
218
|
+
[:oauth_accessor_secret, accessor_secret]
|
219
|
+
]
|
220
|
+
params << [:xoauth_principal, principal.to_s] if principal
|
221
|
+
# set the POST's body as a URL form-encoded string
|
222
|
+
request.set_form(params, MIME_WWW_FORM_URL_ENCODED, charset: 'UTF-8')
|
223
|
+
request['Accept'] = MIME_WWW_FORM_URL_ENCODED
|
224
|
+
# Set a custom User-Agent to help identify these invocation
|
225
|
+
request['User-Agent'] = user_agent_string
|
226
|
+
request
|
227
|
+
end
|
228
|
+
|
229
|
+
# Internal: Handle a response for #retrieve
|
230
|
+
def retrieve_handle_response(response, timestamp, nonce, accessor_secret)
|
231
|
+
case response
|
232
|
+
when Net::HTTPSuccess
|
233
|
+
# Parse the HTTP response and convert it into a Symbol-keyed Hash
|
234
|
+
tuples = Protocol.parse_url_query_string(response.body)
|
235
|
+
# Use the parsed response to construct the AccessToken
|
236
|
+
access_token = AccessToken.new(
|
237
|
+
accessor_secret: accessor_secret,
|
238
|
+
consumer_key: @consumer_key,
|
239
|
+
expires_at: timestamp + tuples[:oauth_expires_in].to_i,
|
240
|
+
nonce: nonce,
|
241
|
+
timestamp: timestamp,
|
242
|
+
token: tuples[:oauth_token],
|
243
|
+
token_secret: tuples[:oauth_token_secret]
|
244
|
+
)
|
245
|
+
access_token
|
246
|
+
else
|
247
|
+
# Extract any OAuth Problems reported in the response
|
248
|
+
oauth_data = Protocol.parse_authorization_header(response['WWW-Authenticate'])
|
249
|
+
# Raise an error for a failure to acquire a token
|
250
|
+
raise OAuthError.new('unable to acquire token', response.code, oauth_data[:oauth_problem])
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Internal: Prepare a request for #retrieve_keys
|
255
|
+
def retrieve_keys_prepare_request(keys_version)
|
256
|
+
request = Net::HTTP::Get.new("#{@access_token_url}/keys/#{keys_version}")
|
257
|
+
request['Accept'] = 'application/json'
|
258
|
+
request['User-Agent'] = user_agent_string
|
259
|
+
request['Authorization'] = retrieve.authorization_header
|
260
|
+
request
|
261
|
+
end
|
262
|
+
|
263
|
+
# Internal: Handle a response for #retrieve_keys
|
264
|
+
def retrieve_keys_handle_response(keys_version, response)
|
265
|
+
case response
|
266
|
+
when Net::HTTPSuccess
|
267
|
+
parsed_response = JSON.parse(response.body)
|
268
|
+
aes_key = parsed_response.dig('aesKey', 'secretKey')
|
269
|
+
raise OAuthError, 'AES secret key retrieved was invalid' unless aes_key
|
270
|
+
rsa_key = parsed_response.dig('rsaKey', 'publicKey')
|
271
|
+
raise OAuthError, 'RSA public key retrieved was invalid' unless rsa_key
|
272
|
+
Keys.new(
|
273
|
+
version: keys_version,
|
274
|
+
aes_secret_key: Base64.decode64(aes_key),
|
275
|
+
rsa_public_key: Base64.decode64(rsa_key)
|
276
|
+
)
|
277
|
+
else
|
278
|
+
# Extract any OAuth Problems reported in the response
|
279
|
+
oauth_data = Protocol.parse_authorization_header(response['WWW-Authenticate'])
|
280
|
+
# Raise an error for a failure to acquire keys
|
281
|
+
raise OAuthError.new('unable to acquire keys', response.code, oauth_data[:oauth_problem])
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
178
285
|
end
|
179
286
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cerner
|
4
|
+
module OAuth1a
|
5
|
+
# Internal: A simple cache abstraction for use by AccessTokenAgent only.
|
6
|
+
class Cache
|
7
|
+
# Internal: A cache entry class for Keys values.
|
8
|
+
class KeysEntry
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(keys, expires_in)
|
12
|
+
@value = keys
|
13
|
+
@expires_at = Time.now.utc.to_i + expires_in
|
14
|
+
end
|
15
|
+
|
16
|
+
def expired?(now)
|
17
|
+
@expires_at <= now
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Internal: A cache entry class for AccessToken values.
|
22
|
+
class AccessTokenEntry
|
23
|
+
attr_reader :value
|
24
|
+
|
25
|
+
def initialize(access_token)
|
26
|
+
@value = access_token
|
27
|
+
end
|
28
|
+
|
29
|
+
def expired?(now)
|
30
|
+
@value.expired?(now: now)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
ONE_HOUR = 3600
|
35
|
+
TWENTY_FOUR_HOURS = 24 * ONE_HOUR
|
36
|
+
|
37
|
+
def initialize(max:)
|
38
|
+
@max = max
|
39
|
+
@lock = Mutex.new
|
40
|
+
@entries = {}
|
41
|
+
end
|
42
|
+
|
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
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def get(key)
|
53
|
+
@lock.synchronize do
|
54
|
+
prune_expired(Time.now.utc.to_i)
|
55
|
+
@entries[key]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def prune_expired(now)
|
62
|
+
return if @entries.empty?
|
63
|
+
|
64
|
+
@entries.delete_if { |_, v| v.expired?(now) }
|
65
|
+
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def prune_size
|
70
|
+
return if @entries.empty? || @entries.size <= @max
|
71
|
+
|
72
|
+
num_to_prune = @entries.size - @max
|
73
|
+
num_to_prune.times { @entries.shift }
|
74
|
+
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'openssl'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module Cerner
|
8
|
+
module OAuth1a
|
9
|
+
# Public: Keys for authenticating Access Tokens by service providers. Keys can be retrieved
|
10
|
+
# via AccessTokenAgent#retrieve_keys.
|
11
|
+
class Keys
|
12
|
+
# Returns the String version identifier of the keys.
|
13
|
+
attr_reader :version
|
14
|
+
# Returns the String AES secret key.
|
15
|
+
attr_reader :aes_secret_key
|
16
|
+
# Returns the String RSA public key.
|
17
|
+
attr_reader :rsa_public_key
|
18
|
+
|
19
|
+
# Public: Constructs an instance.
|
20
|
+
#
|
21
|
+
# arguments - The keyword arguments of the method:
|
22
|
+
# :version - The version identifier of the keys.
|
23
|
+
# :aes_secret_key - The AES secret key.
|
24
|
+
# :rsa_public_key - The RSA public key.
|
25
|
+
#
|
26
|
+
# Raises ArgumentError if version, aes_secret_key or rsa_public_key is nil.
|
27
|
+
def initialize(version:, aes_secret_key:, rsa_public_key:)
|
28
|
+
raise ArgumentError, 'version is nil' unless version
|
29
|
+
raise ArgumentError, 'aes_secret_key is nil' unless aes_secret_key
|
30
|
+
raise ArgumentError, 'rsa_public_key is nil' unless rsa_public_key
|
31
|
+
|
32
|
+
@version = version
|
33
|
+
@aes_secret_key = aes_secret_key
|
34
|
+
@rsa_public_key = rsa_public_key
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Compare this to other based on attributes.
|
38
|
+
#
|
39
|
+
# other - The Keys to compare this to.
|
40
|
+
#
|
41
|
+
# Return true if equal; false otherwise
|
42
|
+
def ==(other)
|
43
|
+
version == other.version &&
|
44
|
+
aes_secret_key == other.aes_secret_key &&
|
45
|
+
rsa_public_key == other.rsa_public_key
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: Compare this to other based on attributes.
|
49
|
+
#
|
50
|
+
# other - The Keys to compare this to.
|
51
|
+
#
|
52
|
+
# Return true if equal; false otherwise
|
53
|
+
def eql?(other)
|
54
|
+
self == other
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Generates a Hash of the attributes.
|
58
|
+
#
|
59
|
+
# Returns a Hash with keys for each attribute.
|
60
|
+
def to_h
|
61
|
+
{
|
62
|
+
version: @version,
|
63
|
+
aes_secret_key: @aes_secret_key,
|
64
|
+
rsa_public_key: @rsa_public_key
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
# Public: Returns the #rsa_public_key as an OpenSSL::PKey::RSA intance.
|
69
|
+
#
|
70
|
+
# Raises OpenSSL::PKey::RSAError if #rsa_public_key is not a valid key
|
71
|
+
def rsa_public_key_as_pkey
|
72
|
+
OpenSSL::PKey::RSA.new(@rsa_public_key)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: Verifies that an oauth_token is authentic based on the #rsa_public_key.
|
76
|
+
#
|
77
|
+
# oauth_token - The oauth_token value to verify.
|
78
|
+
#
|
79
|
+
# Returns true if authentic; false otherwise.
|
80
|
+
#
|
81
|
+
# Raises ArgumentError if oauth_token is nil or invalid
|
82
|
+
# Raises OpenSSL::PKey::RSAError if #rsa_public_key is not a valid key
|
83
|
+
def verify_rsasha1_signature(oauth_token)
|
84
|
+
raise ArgumentError, 'oauth_token is nil' unless oauth_token
|
85
|
+
|
86
|
+
message, raw_sig = oauth_token.split('&RSASHA1=')
|
87
|
+
raise ArgumentError, 'unable to get message out of oauth_token' unless message
|
88
|
+
raise ArgumentError, 'unable to get RSASHA1 signature out of oauth_token' unless raw_sig
|
89
|
+
|
90
|
+
# URL decode value and Base64 (urlsafe) decode that result
|
91
|
+
sig = Base64.urlsafe_decode64(URI.decode_www_form_component(raw_sig))
|
92
|
+
rsa_public_key_as_pkey.verify(OpenSSL::Digest::SHA1.new, sig, message)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Public: Decrypts the HMACSecrets parameter of an oauth_token using the #aes_secret_key.
|
96
|
+
#
|
97
|
+
# hmac_secrets_param - The extracted value of the HMACSecrets parameter of an oauth_token. The
|
98
|
+
# value is assumed to be Base64 (URL safe) encoded.
|
99
|
+
#
|
100
|
+
# Returns the decrypted secrets.
|
101
|
+
#
|
102
|
+
# Raises ArgumentError if oauth_token is nil or invalid
|
103
|
+
def decrypt_hmac_secrets(hmac_secrets_param)
|
104
|
+
raise ArgumentError, 'hmac_secrets_param is nil' unless hmac_secrets_param
|
105
|
+
|
106
|
+
ciphertext = Base64.urlsafe_decode64(hmac_secrets_param)
|
107
|
+
raise ArgumentError, 'hmac_secrets_param does not contain enough data' unless ciphertext.size > 16
|
108
|
+
|
109
|
+
# extract first 16 bytes to get initialization vector
|
110
|
+
iv = ciphertext[0, 16]
|
111
|
+
# trim off the IV
|
112
|
+
ciphertext = ciphertext[16..-1]
|
113
|
+
|
114
|
+
cipher = OpenSSL::Cipher.new('AES-128-CBC')
|
115
|
+
# invoke #decrypt to prep the instance
|
116
|
+
cipher.decrypt
|
117
|
+
cipher.iv = iv
|
118
|
+
cipher.key = @aes_secret_key
|
119
|
+
text = cipher.update(ciphertext) + cipher.final
|
120
|
+
text
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|