cerner-oauth1a 1.0.1 → 2.0.0.rc1
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 +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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 93df035b09b74b1936a6b14305ffa17f80f079deeacd2af9ec1d3f9e8797cd35
|
4
|
+
data.tar.gz: b5b00bf3cae57f9948c117c77aa0a6a0723f8af81d085323788f24508af05309
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00b0f14065ff87925fb372f92b9365fdaaf00e26b737673b68bd2df4ace4cdb50e33ec5a26a32a695f8c9f5871280b9106f9c9edae5045182e9287cf7ad87f06
|
7
|
+
data.tar.gz: 46ce88a5cf635524e619f447c8a95f99d5c240b40213c74839c7c2d1fc261ec5e7f0b02485eee0ddb98525f744a46f09f52cca8c3686919a08a79f360dbfd2e7
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# v2.0.0
|
2
|
+
Added APIs for authenticating Access Tokens, so that service providers can be implemented
|
3
|
+
with this library.
|
4
|
+
|
5
|
+
Behavior changes:
|
6
|
+
* accessor_secret is no longer required to construct a Cerner::OAuth1a::AccessToken
|
7
|
+
* token_secret is no longer required to construct a Cerner::OAuth1a::AccessToken
|
8
|
+
* expires_at is no longer required to construct a Cerner::OAuth1a::AccessToken
|
9
|
+
|
1
10
|
# v1.0.1
|
2
11
|
Correct confusing functionality within AccessToken#expired?, so that it's
|
3
12
|
no longer surprising and backwards.
|
data/NOTICE
CHANGED
data/README.md
CHANGED
@@ -1,32 +1,32 @@
|
|
1
|
-
# Cerner OAuth 1.0a
|
1
|
+
# Cerner OAuth 1.0a Consumer and Service Provider Library
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
[](https://travis-ci.org/cerner/cerner-oauth1a)
|
4
|
+
[](https://rubygems.org/gems/cerner-oauth1a)
|
5
|
+
[](https://codeclimate.com/github/cerner/cerner-oauth1a)
|
6
|
+
[](https://gemnasium.com/cerner/cerner-oauth1a)
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
### `gem` command
|
8
|
+
A minimal dependency library for interacting with a Cerner OAuth 1.0a Access Token Service for
|
9
|
+
invoking Cerner OAuth 1.0a protected services or implementing Cerner OAuth 1.0a authentication.
|
10
|
+
Cerner's OAuth 1.0a Access Token Service provides a means for facilitating two-legged (B2B)
|
11
|
+
authentication via a variant of OAuth 1.0a.
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
### Gemfile
|
13
|
+
# Usage
|
17
14
|
|
18
|
-
|
15
|
+
There are two use cases for working with this library: Consumer and Service Provider. The Consumer
|
16
|
+
Use Case is for invoking services protected by Cerner OAuth 1.0a. The Service Provider Use Case is
|
17
|
+
for implementing a Ruby-based service.
|
19
18
|
|
20
|
-
##
|
19
|
+
## Consumer Use Case
|
21
20
|
|
22
21
|
require 'cerner/oauth1a'
|
23
22
|
require 'net/http'
|
24
23
|
|
25
|
-
# Setup the AccessTokenAgent with an Access Token URL, Key and Secret
|
24
|
+
# Setup the AccessTokenAgent with an Access Token Service's URL, a Key and a Secret
|
26
25
|
agent = Cerner::OAuth1a::AccessTokenAgent.new(
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
access_token_url: 'https://api.cernercare.com/oauth/access',
|
27
|
+
consumer_key: 'CONSUMER_KEY',
|
28
|
+
consumer_secret: 'CONSUMER_SECRET'
|
29
|
+
)
|
30
30
|
|
31
31
|
# Retrieve an AccessToken instance
|
32
32
|
access_token = agent.retrieve
|
@@ -39,7 +39,7 @@ This library can be installed using the `gem` command or added to a Gemfile for
|
|
39
39
|
# Invoke the API's HTTP endpoint and use the AccessToken to generate an Authorization header
|
40
40
|
response = http.request_get(uri.path, Authorization: access_token.authorization_header)
|
41
41
|
|
42
|
-
|
42
|
+
### Access Token Reuse
|
43
43
|
Generally, you'll want to use an Access Token more than once. Access Tokens can be reused, but
|
44
44
|
they do expire, so you'll need to acquire new tokens after one expires. All of the expiration
|
45
45
|
information is contained in the AccessToken class and you can easily determine if a token is
|
@@ -54,16 +54,70 @@ implement that:
|
|
54
54
|
|
55
55
|
response = http.request_get(uri.path, Authorization: access_token.authorization_header)
|
56
56
|
|
57
|
+
## Service Provider Use Case
|
58
|
+
|
59
|
+
# Acquire Authorization header value from HTTP server's request
|
60
|
+
authz_header = request['Authorization']
|
61
|
+
|
62
|
+
# Parse the header value
|
63
|
+
access_token = AccessToken.from_authorization_header(authz_header)
|
64
|
+
|
65
|
+
# Authenticate the Access Token
|
66
|
+
# Note: An AccessTokenAgent, configured with a System Account that has been granted privileges
|
67
|
+
# to Acquire Tokens and Process Tokens.
|
68
|
+
begin
|
69
|
+
results = access_token.authenticate(agent)
|
70
|
+
rescue OAuthError => e
|
71
|
+
# respond with a 401
|
72
|
+
end
|
73
|
+
|
74
|
+
# Use Consumer Key (i.e. the System Account) to do further authorization, as appropriate
|
75
|
+
system_account_id = access_token.consumer_key
|
76
|
+
|
77
|
+
# Optionally, extract additional parameters sent with the token, such as Consumer.Principal
|
78
|
+
# (xoauth_principal)
|
79
|
+
consumer_principal = results[:"Consumer.Principal"]
|
80
|
+
|
57
81
|
## References
|
58
82
|
* https://wiki.ucern.com/display/public/reference/Cerner%27s+OAuth+Specification
|
59
83
|
* http://oauth.net/core/1.0a
|
60
84
|
* http://oauth.pbwiki.com/ProblemReporting
|
61
85
|
* https://wiki.ucern.com/display/public/reference/Accessing+Cerner%27s+Web+Services+Using+OAuth+1.0a
|
62
86
|
|
87
|
+
# Installing
|
88
|
+
This library can be installed using the `gem` command or added to a Gemfile for use with Bundler.
|
89
|
+
|
90
|
+
## `gem` command
|
91
|
+
|
92
|
+
$ gem install cerner-oauth1a
|
93
|
+
|
94
|
+
## Gemfile
|
95
|
+
|
96
|
+
gem 'cerner-oauth1a', '~> 2.0'
|
97
|
+
|
63
98
|
# Building
|
64
99
|
|
65
|
-
This project is built using Ruby 2.
|
66
|
-
is utilized for test coverage.
|
100
|
+
This project is built using Ruby 2.4+, Rake and Bundler. RSpec is used for unit tests and SimpleCov
|
101
|
+
is utilized for test coverage. RuboCop is used to monitor the lint and style.
|
102
|
+
|
103
|
+
## Setup
|
104
|
+
|
105
|
+
To setup the development workspace, run the following after checkout:
|
106
|
+
|
107
|
+
gem install bundler
|
108
|
+
bundle install
|
109
|
+
|
110
|
+
## Tests
|
111
|
+
|
112
|
+
To run the RSpec tests, run the following:
|
113
|
+
|
114
|
+
bin/rspec
|
115
|
+
|
116
|
+
## Lint
|
117
|
+
|
118
|
+
To analyze the project's style and lint, run the following:
|
119
|
+
|
120
|
+
bin/rubocop
|
67
121
|
|
68
122
|
# Availability
|
69
123
|
|
@@ -79,7 +133,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
79
133
|
|
80
134
|
# LICENSE
|
81
135
|
|
82
|
-
Copyright
|
136
|
+
Copyright 2018 Cerner Innovation, Inc.
|
83
137
|
|
84
138
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
85
139
|
|
data/lib/cerner/oauth1a.rb
CHANGED
@@ -1,92 +1,216 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cerner/oauth1a/oauth_error'
|
4
|
+
require 'cerner/oauth1a/protocol'
|
5
|
+
require 'uri'
|
6
|
+
|
1
7
|
module Cerner
|
2
8
|
module OAuth1a
|
3
9
|
|
4
|
-
# Public:
|
10
|
+
# Public: A Cerner OAuth 1.0a Access Token and related request parameters for use in Consumer or
|
11
|
+
# Service Provider use cases.
|
5
12
|
class AccessToken
|
13
|
+
# Public: Constructs an AccessToken using the value of an HTTP Authorization Header based on
|
14
|
+
# the OAuth HTTP Authorization Scheme (https://oauth.net/core/1.0a/#auth_header).
|
15
|
+
#
|
16
|
+
# value - A String containing the HTTP Authorization Header value.
|
17
|
+
#
|
18
|
+
# Returns an AccessToken.
|
19
|
+
#
|
20
|
+
# Raises a Cerner::OAuth1a::OAuthError with a populated oauth_problem if any of the parameters
|
21
|
+
# in the value are invalid.
|
22
|
+
def self.from_authorization_header(value)
|
23
|
+
params = Protocol.parse_authorization_header(value)
|
24
|
+
|
25
|
+
raise OAuthError.new('', nil, 'version_rejected') unless params[:oauth_version]&.eql?('1.0')
|
26
|
+
|
27
|
+
missing_params = []
|
28
|
+
consumer_key = params[:oauth_consumer_key]
|
29
|
+
missing_params << :oauth_consumer_key unless consumer_key&.empty?
|
30
|
+
nonce = params[:oauth_nonce]
|
31
|
+
missing_params << :oauth_nonce unless nonce&.empty?
|
32
|
+
timestamp = params[:oauth_timestamp]
|
33
|
+
missing_params << :oauth_timestamp unless timestamp&.empty?
|
34
|
+
token = params[:oauth_token]
|
35
|
+
missing_params << :oauth_token unless token&.empty?
|
36
|
+
signature_method = params[:oauth_signature_method]
|
37
|
+
missing_params << :oauth_signature_method unless signature_method&.empty?
|
38
|
+
signature = params[:oauth_signature]
|
39
|
+
missing_params << :oauth_signature unless signature&.empty?
|
40
|
+
|
41
|
+
raise OAuthError.new('', nil, 'parameter_absent', missing_params) unless missing_params.empty?
|
42
|
+
|
43
|
+
AccessToken.new(
|
44
|
+
consumer_key: consumer_key,
|
45
|
+
nonce: nonce,
|
46
|
+
timestamp: timestamp,
|
47
|
+
token: token,
|
48
|
+
signature_method: signature_method,
|
49
|
+
signature: signature
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
6
53
|
# Returns the String Accessor Secret related to this token.
|
7
54
|
attr_reader :accessor_secret
|
8
|
-
# Returns the String Consumer Key related to this token.
|
55
|
+
# Returns the String Consumer Key (oauth_consumer_key) related to this token.
|
9
56
|
attr_reader :consumer_key
|
10
57
|
# Returns the Time this token expires at.
|
11
58
|
attr_reader :expires_at
|
12
|
-
# Returns the String nonce related to this token.
|
59
|
+
# Returns the String nonce (oauth_nonce) related to this token.
|
13
60
|
attr_reader :nonce
|
14
|
-
# Returns the Time this token was created.
|
61
|
+
# Returns the Time this token was created (oauth_timestamp).
|
15
62
|
attr_reader :timestamp
|
16
|
-
# Returns the String Token.
|
63
|
+
# Returns the String Token (oauth_token).
|
17
64
|
attr_reader :token
|
18
65
|
# Returns the String Token Secret related to this token.
|
19
66
|
attr_reader :token_secret
|
67
|
+
# Returns the String Signature Method (oauth_signature_method) related to this token.
|
68
|
+
attr_reader :signature_method
|
69
|
+
# Returns the String Signature (oauth_signature) related to this token.
|
70
|
+
attr_reader :signature
|
20
71
|
|
21
72
|
# Public: Constructs an instance.
|
22
73
|
#
|
23
74
|
# arguments - The keyword arguments of the method:
|
24
|
-
# :accessor_secret
|
25
|
-
# :consumer_key
|
26
|
-
# :expires_at
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# :nonce
|
30
|
-
# :
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# :token
|
34
|
-
# :token_secret
|
35
|
-
#
|
36
|
-
#
|
37
|
-
|
38
|
-
|
75
|
+
# :accessor_secret - The optional String representing the accessor secret.
|
76
|
+
# :consumer_key - The required String representing the consumer key.
|
77
|
+
# :expires_at - An optional Time representing the expiration moment or any
|
78
|
+
# object responding to to_i that represents the expiration
|
79
|
+
# moment as the number of seconds since the epoch.
|
80
|
+
# :nonce - The required String representing the nonce.
|
81
|
+
# :timestamp - A required Time representing the creation moment or any
|
82
|
+
# object responding to to_i that represents the creation
|
83
|
+
# moment as the number of seconds since the epoch.
|
84
|
+
# :token - The required String representing the token.
|
85
|
+
# :token_secret - The required String representing the token secret.
|
86
|
+
# :signature_method - The optional String representing the signature method.
|
87
|
+
# Defaults to PLAINTEXT.
|
88
|
+
# :signature - The optional String representing the signature.
|
89
|
+
# Defaults to nil.
|
90
|
+
#
|
91
|
+
# Raises ArgumentError if consumer_key, nonce, timestamp, token or signature_method is nil.
|
92
|
+
def initialize(
|
93
|
+
accessor_secret: nil,
|
94
|
+
consumer_key:,
|
95
|
+
expires_at: nil,
|
96
|
+
nonce:,
|
97
|
+
signature: nil,
|
98
|
+
signature_method: 'PLAINTEXT',
|
99
|
+
timestamp:,
|
100
|
+
token:,
|
101
|
+
token_secret: nil
|
102
|
+
)
|
39
103
|
raise ArgumentError, 'consumer_key is nil' unless consumer_key
|
40
|
-
raise ArgumentError, 'expires_at is nil' unless expires_at
|
41
104
|
raise ArgumentError, 'nonce is nil' unless nonce
|
42
105
|
raise ArgumentError, 'timestamp is nil' unless timestamp
|
43
106
|
raise ArgumentError, 'token is nil' unless token
|
44
|
-
raise ArgumentError, 'token_secret is nil' unless token_secret
|
45
107
|
|
46
|
-
@accessor_secret = accessor_secret
|
108
|
+
@accessor_secret = accessor_secret || nil
|
109
|
+
@authorization_header = nil
|
47
110
|
@consumer_key = consumer_key
|
48
|
-
@expires_at = convert_to_time(expires_at)
|
111
|
+
@expires_at = expires_at ? convert_to_time(expires_at) : nil
|
49
112
|
@nonce = nonce
|
113
|
+
@signature = signature
|
114
|
+
@signature_method = signature_method || 'PLAINTEXT'
|
50
115
|
@timestamp = convert_to_time(timestamp)
|
51
116
|
@token = token
|
52
|
-
@token_secret = token_secret
|
53
|
-
@authorization_header = nil
|
117
|
+
@token_secret = token_secret || nil
|
54
118
|
end
|
55
119
|
|
56
|
-
# Public: Generates a value suitable for use as an HTTP Authorization header.
|
120
|
+
# Public: Generates a value suitable for use as an HTTP Authorization header. If #signature is
|
121
|
+
# nil, then #accessor_secret and #token_secret will be used to build a signature via the
|
122
|
+
# PLAINTEXT method.
|
57
123
|
#
|
58
124
|
# Returns a String representation of the access token.
|
125
|
+
#
|
126
|
+
# Raises Cerner::OAuth1a::OAuthError if #signature_method is not PLAINTEXT or if a signature
|
127
|
+
# can't be determined.
|
59
128
|
def authorization_header
|
60
129
|
return @authorization_header if @authorization_header
|
61
130
|
|
131
|
+
unless @signature_method == 'PLAINTEXT'
|
132
|
+
raise OAuthError.new('signature_method must be PLAINTEXT', nil, 'signature_method_rejected')
|
133
|
+
end
|
134
|
+
|
135
|
+
if @signature
|
136
|
+
sig = @signature
|
137
|
+
elsif @accessor_secret && @token_secret
|
138
|
+
sig = "#{@accessor_secret}&#{@token_secret}"
|
139
|
+
else
|
140
|
+
raise OAuthError.new('accessor_secret or token_secret is nil', nil, 'parameter_absent')
|
141
|
+
end
|
142
|
+
|
62
143
|
tuples = {
|
63
144
|
oauth_version: '1.0',
|
64
|
-
oauth_signature_method:
|
65
|
-
oauth_signature:
|
145
|
+
oauth_signature_method: @signature_method,
|
146
|
+
oauth_signature: sig,
|
66
147
|
oauth_consumer_key: @consumer_key,
|
67
148
|
oauth_nonce: @nonce,
|
68
149
|
oauth_timestamp: @timestamp.tv_sec,
|
69
150
|
oauth_token: @token
|
70
151
|
}
|
71
|
-
@authorization_header =
|
152
|
+
@authorization_header = Protocol.generate_authorization_header(tuples)
|
72
153
|
end
|
73
154
|
|
74
|
-
# Public:
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
155
|
+
# Public: Authenticates the #token against the #consumer_key, #signature and side-channel
|
156
|
+
# secrets exchange via AccessTokenAgent#retrieve_keys.
|
157
|
+
#
|
158
|
+
# access_token_agent - An instance of Cerner::OAuth1a::AccessTokenAgent configured with
|
159
|
+
# appropriate credentials to retrieve secrets via
|
160
|
+
# Cerner::OAuth1a::AccessTokenAgent#retrieve_keys.
|
78
161
|
#
|
79
|
-
#
|
162
|
+
# Returns a Hash (symbolized keys) of any extra parameters in #token if authentication succeeds.
|
163
|
+
#
|
164
|
+
# Raises ArgumentError if access_token_agent is nil
|
165
|
+
# Raises Cerner::OAuth1a::OAuthError with an oauth_problem if authentication fails.
|
166
|
+
def authenticate(access_token_agent)
|
167
|
+
raise ArgumentError, 'access_token_agent is nil' unless access_token_agent
|
168
|
+
|
169
|
+
unless @signature_method == 'PLAINTEXT'
|
170
|
+
raise OAuthError.new('signature_method must be PLAINTEXT', nil, 'signature_method_rejected')
|
171
|
+
end
|
172
|
+
|
173
|
+
tuples = Protocol.parse_url_query_string(@token)
|
174
|
+
|
175
|
+
unless @consumer_key == tuples.delete(:ConsumerKey)
|
176
|
+
raise OAuthError.new('consumer keys do not match', nil, 'consumer_key_rejected')
|
177
|
+
end
|
178
|
+
|
179
|
+
verify_expiration(tuples.delete(:ExpiresOn))
|
180
|
+
|
181
|
+
keys = load_keys(access_token_agent, tuples.delete(:KeysVersion))
|
182
|
+
|
183
|
+
verify_token(keys)
|
184
|
+
# RSASHA1 param gets consumed in #verify_token, so remove it too
|
185
|
+
tuples.delete(:RSASHA1)
|
186
|
+
|
187
|
+
verify_signature(keys, tuples.delete(:HMACSecrets))
|
188
|
+
|
189
|
+
tuples
|
190
|
+
end
|
191
|
+
|
192
|
+
# Public: Check whether the access token has expired, if #expires_at is not nil. By default
|
193
|
+
# (with no arguments), the method checks whether the token has expired based on the current
|
194
|
+
# time and a fudge factor of 300 seconds (5 minutes). Non-default argument values can be used
|
195
|
+
# to see whether the access token has expired at a different time and with a different fudge
|
196
|
+
# factor.
|
197
|
+
#
|
198
|
+
# now - A Time instance to check the expiration information against. Defaults to
|
199
|
+
# Time.now.
|
80
200
|
# fudge_sec - The number of seconds to remove from #expires_at to adjust the comparison.
|
81
201
|
#
|
82
|
-
# Returns true if the access token
|
202
|
+
# Returns true if the access token is expired or #expires_at is nil; false otherwise
|
83
203
|
def expired?(now: Time.now, fudge_sec: 300)
|
204
|
+
# if @expires_at is nil, return true now
|
205
|
+
return true unless @expires_at
|
84
206
|
now = convert_to_time(now)
|
85
|
-
now.tv_sec >= expires_at.tv_sec - fudge_sec
|
207
|
+
now.tv_sec >= @expires_at.tv_sec - fudge_sec
|
86
208
|
end
|
87
209
|
|
88
210
|
# Public: Compare this to other based on attributes.
|
89
211
|
#
|
212
|
+
# other - The AccessToken to compare this to.
|
213
|
+
#
|
90
214
|
# Return true if equal; false otherwise
|
91
215
|
def ==(other)
|
92
216
|
accessor_secret == other.accessor_secret &&
|
@@ -95,11 +219,15 @@ module Cerner
|
|
95
219
|
nonce == other.nonce &&
|
96
220
|
timestamp == other.timestamp &&
|
97
221
|
token == other.token &&
|
98
|
-
token_secret == other.token_secret
|
222
|
+
token_secret == other.token_secret &&
|
223
|
+
signature_method == other.signature_method &&
|
224
|
+
signature == other.signature
|
99
225
|
end
|
100
226
|
|
101
227
|
# Public: Compare this to other based on the attributes. Equivalent to calling #==.
|
102
228
|
#
|
229
|
+
# other - The AccessToken to compare this to.
|
230
|
+
#
|
103
231
|
# Return true if equal; false otherwise
|
104
232
|
def eql?(other)
|
105
233
|
self == other
|
@@ -116,14 +244,19 @@ module Cerner
|
|
116
244
|
nonce: @nonce,
|
117
245
|
timestamp: @timestamp,
|
118
246
|
token: @token,
|
119
|
-
token_secret: @token_secret
|
247
|
+
token_secret: @token_secret,
|
248
|
+
signature_method: @signature_method,
|
249
|
+
signature: @signature
|
120
250
|
}
|
121
251
|
end
|
122
252
|
|
123
253
|
private
|
124
254
|
|
125
255
|
# Internal: Used by #initialize and #expired? to convert data into a Time instance.
|
126
|
-
#
|
256
|
+
#
|
257
|
+
# time - Time or any object with a #to_i the returns an Integer.
|
258
|
+
#
|
259
|
+
# Returns a Time instance in the UTC time zone.
|
127
260
|
def convert_to_time(time)
|
128
261
|
raise ArgumentError, 'time is nil' unless time
|
129
262
|
if time.is_a? Time
|
@@ -132,7 +265,57 @@ module Cerner
|
|
132
265
|
Time.at(time.to_i).utc
|
133
266
|
end
|
134
267
|
end
|
135
|
-
end
|
136
268
|
|
269
|
+
# Internal: Used by #authenticate to verify the expiration time.
|
270
|
+
#
|
271
|
+
# expires_on - The ExpiresOn parameter of oauth_token
|
272
|
+
#
|
273
|
+
# Raises OAuthError if the parameter is invalid or expired
|
274
|
+
def verify_expiration(expires_on)
|
275
|
+
raise OAuthError.new('token missing ExpiresOn', nil, 'oauth_parameters_rejected') unless expires_on
|
276
|
+
expires_on = convert_to_time(expires_on)
|
277
|
+
now = convert_to_time(Time.now)
|
278
|
+
raise OAuthError.new('token has expired', nil, 'token_expired') if now.tv_sec >= expires_on.tv_sec
|
279
|
+
end
|
280
|
+
|
281
|
+
def load_keys(access_token_agent, keys_version)
|
282
|
+
raise OAuthError.new('token missing KeysVersion', nil, 'oauth_parameters_rejected') unless keys_version
|
283
|
+
access_token_agent.retrieve_keys(keys_version)
|
284
|
+
end
|
285
|
+
|
286
|
+
# Internal: Used by #authenticate to verify the oauth_token value.
|
287
|
+
#
|
288
|
+
# keys - The Keys instance that contains the key used to sign the oauth_token
|
289
|
+
#
|
290
|
+
# Raises OAuthError if the parameter is not authentic
|
291
|
+
def verify_token(keys)
|
292
|
+
unless keys.verify_rsasha1_signature(@token)
|
293
|
+
raise OAuthError.new('token is not authentic', nil, 'oauth_parameters_rejected')
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Internal: Used by #authenticate to verify the request signature.
|
298
|
+
#
|
299
|
+
# keys - The Keys instance that contains the key used to encrypt the HMACSecrets
|
300
|
+
# hmac_secrets - The HMACSecrets parameter of oauth_token
|
301
|
+
#
|
302
|
+
# Raises OAuthError if there is no signature, the parameter is invalid or the signature does
|
303
|
+
# not match the secrets
|
304
|
+
def verify_signature(keys, hmac_secrets)
|
305
|
+
raise OAuthError.new('missing signature', nil, 'oauth_parameters_absent') unless @signature
|
306
|
+
raise OAuthError.new('missing HMACSecrets', nil, 'oauth_parameters_rejected') unless hmac_secrets
|
307
|
+
|
308
|
+
begin
|
309
|
+
secrets = keys.decrypt_hmac_secrets(hmac_secrets)
|
310
|
+
rescue ArgumentError, OpenSSL::PKey::RSAError => e
|
311
|
+
raise OAuthError.new("unable to decrypt HMACSecrets: #{e.message}", nil, 'oauth_parameters_rejected')
|
312
|
+
end
|
313
|
+
|
314
|
+
secrets_parts = Protocol.parse_url_query_string(secrets)
|
315
|
+
expected_signature = "#{secrets_parts[:ConsumerSecret]}&#{secrets_parts[:TokenSecret]}"
|
316
|
+
|
317
|
+
raise OAuthError.new('signature is not valid', nil, 'signature_invalid') unless @signature == expected_signature
|
318
|
+
end
|
319
|
+
end
|
137
320
|
end
|
138
321
|
end
|