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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 11d5187c40396ed12957bfbf5bb06a95b6473276
4
- data.tar.gz: 721aff2530e06b363d38570fa1f4c3de2f456ebc
2
+ SHA256:
3
+ metadata.gz: 93df035b09b74b1936a6b14305ffa17f80f079deeacd2af9ec1d3f9e8797cd35
4
+ data.tar.gz: b5b00bf3cae57f9948c117c77aa0a6a0723f8af81d085323788f24508af05309
5
5
  SHA512:
6
- metadata.gz: b2339a067f8ff9360a0c6e70b4656c2cd44e6f74f044eae55feb19670f2aba37bd74fb95af0df30ba6307e66a7b177a5950c056c147948d907db074ab14f628f
7
- data.tar.gz: 15a89f6cc5c896428e9346df144d3a659c8e6577a0ffb971a5349c6e3131d2249e4b70f87d71632bd3a0de35ced170f53731f029002502b5ec3a9d87bd7bf16a
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
@@ -1,4 +1,4 @@
1
- Copyright 2017 Cerner Innovation, Inc.
1
+ Copyright 2018 Cerner Innovation, Inc.
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
data/README.md CHANGED
@@ -1,32 +1,32 @@
1
- # Cerner OAuth 1.0a Client Library
1
+ # Cerner OAuth 1.0a Consumer and Service Provider Library
2
2
 
3
- This RubyGem is a client library for interacting with the Cerner OAuth 1.0a provider to
4
- participate in two-legged (B2B) authentication. The goal of this project is to provide a zero-dependency Ruby library that simply and compactly implements the client aspects of
5
- Cerner OAuth 1.0a variant of the OAuth 1.0a B2B workflow.
3
+ [![Build Status](https://api.travis-ci.org/cerner/cerner-oauth1a.svg)](https://travis-ci.org/cerner/cerner-oauth1a)
4
+ [![Gem Version](http://img.shields.io/gem/v/cerner-oauth1a.svg)](https://rubygems.org/gems/cerner-oauth1a)
5
+ [![Code Climate](http://img.shields.io/codeclimate/github/cerner/cerner-oauth1a.svg)](https://codeclimate.com/github/cerner/cerner-oauth1a)
6
+ [![Dependencies Status](http://img.shields.io/gemnasium/cerner/cerner-oauth1a.svg)](https://gemnasium.com/cerner/cerner-oauth1a)
6
7
 
7
- # Usage
8
-
9
- ## Install
10
- This library can be installed using the `gem` command or added to a Gemfile for use with Bundler.
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
- $ gem install cerner-oauth1a
15
-
16
- ### Gemfile
13
+ # Usage
17
14
 
18
- gem 'cerner-oauth1a', '~> 1.0'
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
- ## Basic Use
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
- access_token_url: 'https://api.cernercare.com/oauth/access',
28
- consumer_key: 'CONSUMER_KEY',
29
- consumer_secret: 'CONSUMER_SECRET')
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
- ## Access Token Reuse
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.2+, Rake and Bundler. RSpec is used for unit tests and SimpleCov
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 2017 Cerner Innovation, Inc.
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
 
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cerner/oauth1a/access_token'
2
4
  require 'cerner/oauth1a/access_token_agent'
5
+ require 'cerner/oauth1a/keys'
6
+ require 'cerner/oauth1a/protocol'
3
7
  require 'cerner/oauth1a/version'
@@ -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: An OAuth 1.0a Access Token.
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 - The String representing the accessor secret.
25
- # :consumer_key - The String representing the consumer key.
26
- # :expires_at - A Time representing the expiration moment or any object
27
- # responding to to_i that represents the expiration moment
28
- # as the number of seconds since the epoch.
29
- # :nonce - The String representing the nonce.
30
- # :expires_at - A Time representing the creation moment or any object
31
- # responding to to_i that represents the creation moment
32
- # as the number of seconds since the epoch.
33
- # :token - The String representing the token.
34
- # :token_secret - The String representing the token secret.
35
- #
36
- # Raises ArgumentError if any of the arguments is nil
37
- def initialize(accessor_secret:, consumer_key:, expires_at:, nonce:, timestamp:, token:, token_secret:)
38
- raise ArgumentError, 'accessor_secret is nil' unless accessor_secret
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: 'PLAINTEXT',
65
- oauth_signature: "#{@accessor_secret}&#{@token_secret}",
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 = "OAuth " + tuples.map { |k, v| "#{k}=\"#{URI.encode_www_form_component(v)}\"" }.join(', ')
152
+ @authorization_header = Protocol.generate_authorization_header(tuples)
72
153
  end
73
154
 
74
- # Public: Check whether the access token has expired. By default (with no arguments),
75
- # the method checks whether the token has expired based on the current time and a fudge
76
- # factor of 300 seconds (5 minutes). Non-default argument values can be used to see whether the
77
- # access token has expired at a different time and with a different fudge factor.
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
- # now - A Time instance to check the expiration information against. Default is Time.now.
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 as expired; false otherwise
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
- # Returns a Time instance in the UTC time zone
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