signet 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 0.4.0
2
+
3
+ * Added OAuth 1 server implementation
4
+ * Updated Faraday dependency
5
+
1
6
  # 0.3.4
2
7
 
3
8
  * Attempts to auto-detect CA cert location
data/Gemfile CHANGED
@@ -1,20 +1,20 @@
1
1
  source :rubygems
2
2
 
3
3
  gem 'addressable', '>= 2.2.3'
4
- gem 'faraday', '~> 0.7.0'
5
- gem 'multi_json', '>= 1.3.0'
6
- gem 'jwt', '>= 0.1.4'
4
+ gem 'faraday', '~> 0.8.1'
5
+ gem 'multi_json', '>= 1.0.0'
6
+ gem 'jwt', '>= 0.1.5'
7
7
  gem 'extlib', '>= 0.9.15'
8
8
  gem 'jruby-openssl', :platforms => :jruby
9
9
 
10
10
  group :development do
11
- gem 'launchy'
11
+ gem 'launchy', '>= 2.0.0'
12
12
  gem 'yard'
13
13
  gem 'redcarpet'
14
14
  end
15
15
 
16
16
  group :test, :development do
17
17
  gem 'rake', '>= 0.9.0'
18
- gem 'rspec', '~> 1.2.9'
18
+ gem 'rspec', '>= 2.11.0'
19
19
  gem 'rcov', '>= 0.9.9', :platform => :mri_18
20
20
  end
data/Gemfile.lock CHANGED
@@ -3,11 +3,10 @@ GEM
3
3
  specs:
4
4
  addressable (2.2.8)
5
5
  bouncy-castle-java (1.5.0146.1)
6
+ diff-lcs (1.1.3)
6
7
  extlib (0.9.15)
7
- faraday (0.7.6)
8
- addressable (~> 2.2)
8
+ faraday (0.8.1)
9
9
  multipart-post (~> 1.1)
10
- rack (~> 1.1)
11
10
  ffi (1.0.11-java)
12
11
  jruby-openssl (0.7.7)
13
12
  bouncy-castle-java (>= 1.5.0146.1)
@@ -23,11 +22,17 @@ GEM
23
22
  spoon (~> 0.0.1)
24
23
  multi_json (1.3.5)
25
24
  multipart-post (1.1.5)
26
- rack (1.4.1)
27
25
  rake (0.9.2.2)
28
26
  rcov (1.0.0)
29
27
  redcarpet (2.1.1)
30
- rspec (1.2.9)
28
+ rspec (2.10.0)
29
+ rspec-core (~> 2.10.0)
30
+ rspec-expectations (~> 2.10.0)
31
+ rspec-mocks (~> 2.10.0)
32
+ rspec-core (2.10.0)
33
+ rspec-expectations (2.10.0)
34
+ diff-lcs (~> 1.1.3)
35
+ rspec-mocks (2.10.0)
31
36
  spoon (0.0.1)
32
37
  yard (0.8.1)
33
38
 
@@ -38,7 +43,7 @@ PLATFORMS
38
43
  DEPENDENCIES
39
44
  addressable (>= 2.2.3)
40
45
  extlib (>= 0.9.15)
41
- faraday (~> 0.7.0)
46
+ faraday (~> 0.8.1)
42
47
  jruby-openssl
43
48
  jwt (>= 0.1.4)
44
49
  launchy
@@ -46,5 +51,5 @@ DEPENDENCIES
46
51
  rake (>= 0.9.0)
47
52
  rcov (>= 0.9.9)
48
53
  redcarpet
49
- rspec (~> 1.2.9)
54
+ rspec (~> 2.10.0)
50
55
  yard
data/README.md CHANGED
@@ -19,6 +19,7 @@ Signet is an OAuth 1.0 / OAuth 2.0 implementation.
19
19
  - {Signet::OAuth1}
20
20
  - {Signet::OAuth1::Client}
21
21
  - {Signet::OAuth1::Credential}
22
+ - {Signet::OAuth1::Server}
22
23
  - {Signet::OAuth2}
23
24
  - {Signet::OAuth2::Client}
24
25
 
data/Rakefile CHANGED
@@ -5,14 +5,6 @@ $:.uniq!
5
5
  require 'rubygems'
6
6
  require 'rake'
7
7
 
8
- begin
9
- require 'spec/rake/spectask'
10
- rescue LoadError
11
- STDERR.puts "Please install rspec:"
12
- STDERR.puts "sudo gem install rspec"
13
- exit(1)
14
- end
15
-
16
8
  require File.join(File.dirname(__FILE__), 'lib/signet', 'version')
17
9
 
18
10
  PKG_DISPLAY_NAME = 'Signet'
@@ -36,11 +28,11 @@ PKG_FILES = FileList[
36
28
  "[A-Z]*", "Rakefile"
37
29
  ].exclude(/database\.yml/).exclude(/[_\.]git$/)
38
30
 
39
- RCOV_ENABLED = !!(RUBY_PLATFORM != "java" && RUBY_VERSION =~ /^1\.8/)
31
+ RCOV_ENABLED = !!(RUBY_PLATFORM != 'java' && RUBY_VERSION =~ /^1\.8/)
40
32
  if RCOV_ENABLED
41
- task :default => "spec:verify"
33
+ task :default => 'spec:rcov'
42
34
  else
43
- task :default => "spec"
35
+ task :default => 'spec:normal'
44
36
  end
45
37
 
46
38
  WINDOWS = (RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/) rescue false
data/lib/signet/errors.rb CHANGED
@@ -27,7 +27,14 @@ module Signet
27
27
  end
28
28
 
29
29
  ##
30
- # An error indicating the server refused to authorize the client.
30
+ # An error indicating that the server considers the Authorization header to
31
+ # be malformed(missing/unsupported/invalid parameters), and the request
32
+ # should be considered invalid.
33
+ class MalformedAuthorizationError < StandardError
34
+ end
35
+
36
+ ##
37
+ # An error indicating the remote server refused to authorize the client.
31
38
  class AuthorizationError < StandardError
32
39
  ##
33
40
  # Creates a new authentication error.
@@ -12,7 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- gem 'faraday', '~> 0.7.0'
15
+ gem 'faraday', '~> 0.8.1'
16
16
  require 'faraday'
17
17
  require 'faraday/utils'
18
18
 
@@ -538,7 +538,8 @@ module Signet
538
538
  options = {
539
539
  :signature_method => 'HMAC-SHA1',
540
540
  :additional_parameters => [],
541
- :realm => nil
541
+ :realm => nil,
542
+ :connection => Faraday.default_connection
542
543
  }.merge(options)
543
544
  method = :post
544
545
  parameters = ::Signet::OAuth1.unsigned_temporary_credential_parameters(
@@ -565,8 +566,10 @@ module Signet
565
566
  headers << ['Content-Type', 'application/x-www-form-urlencoded']
566
567
  headers << ['Content-Length', '0']
567
568
  end
568
- return Faraday::Request.create(method.to_s.downcase.to_sym) do |req|
569
- req.url(Addressable::URI.parse(self.temporary_credential_uri.to_str))
569
+ return options[:connection].build_request(method.to_s.downcase.to_sym) do |req|
570
+ req.url(Addressable::URI.parse(
571
+ self.temporary_credential_uri.to_str
572
+ ).normalize.to_s)
570
573
  req.headers = Faraday::Utils::Headers.new(headers)
571
574
  end
572
575
  end
@@ -603,6 +606,7 @@ module Signet
603
606
  options[:connection] ||= Faraday.default_connection
604
607
  request = self.generate_temporary_credential_request(options)
605
608
  request_env = request.to_env(options[:connection])
609
+ request_env[:request] ||= request
606
610
  response = options[:connection].app.call(request_env)
607
611
  if response.status.to_i == 200
608
612
  return ::Signet::OAuth1.parse_form_encoded_credentials(response.body)
@@ -689,7 +693,8 @@ module Signet
689
693
  end
690
694
  options = {
691
695
  :signature_method => 'HMAC-SHA1',
692
- :realm => nil
696
+ :realm => nil,
697
+ :connection => Faraday.default_connection
693
698
  }.merge(options)
694
699
  method = :post
695
700
  parameters = ::Signet::OAuth1.unsigned_token_credential_parameters(
@@ -718,8 +723,10 @@ module Signet
718
723
  headers << ['Content-Type', 'application/x-www-form-urlencoded']
719
724
  headers << ['Content-Length', '0']
720
725
  end
721
- return Faraday::Request.create(method.to_s.downcase.to_sym) do |req|
722
- req.url(Addressable::URI.parse(self.token_credential_uri.to_str))
726
+ return options[:connection].build_request(method.to_s.downcase.to_sym) do |req|
727
+ req.url(Addressable::URI.parse(
728
+ self.token_credential_uri.to_str
729
+ ).normalize.to_s)
723
730
  req.headers = Faraday::Utils::Headers.new(headers)
724
731
  end
725
732
  end
@@ -754,6 +761,7 @@ module Signet
754
761
  options[:connection] ||= Faraday.default_connection
755
762
  request = self.generate_token_credential_request(options)
756
763
  request_env = request.to_env(options[:connection])
764
+ request_env[:request] ||= request
757
765
  response = options[:connection].app.call(request_env)
758
766
  if response.status.to_i == 200
759
767
  return ::Signet::OAuth1.parse_form_encoded_credentials(response.body)
@@ -889,9 +897,8 @@ module Signet
889
897
  raise TypeError, "Expected String, got #{body.class}."
890
898
  end
891
899
  method = method.to_s.downcase.to_sym
892
-
893
- request = Faraday::Request.create(method) do |req|
894
- req.url(Addressable::URI.parse(uri))
900
+ request = options[:connection].build_request(method) do |req|
901
+ req.url(Addressable::URI.parse(uri).normalize.to_s)
895
902
  req.headers = Faraday::Utils::Headers.new(headers)
896
903
  req.body = body
897
904
  end
@@ -978,6 +985,7 @@ module Signet
978
985
  options[:connection] ||= Faraday.default_connection
979
986
  request = self.generate_authenticated_request(options)
980
987
  request_env = request.to_env(options[:connection])
988
+ request_env[:request] ||= request
981
989
  response = options[:connection].app.call(request_env)
982
990
  if response.status.to_i == 401
983
991
  # When accessing a protected resource, we only want to raise an
@@ -0,0 +1,505 @@
1
+ # Copyright (C) 2011 The Yakima Herald-Republic.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ require 'faraday'
16
+
17
+ require 'stringio'
18
+ require 'addressable/uri'
19
+ require 'signet'
20
+ require 'signet/errors'
21
+ require 'signet/oauth_1'
22
+ require 'signet/oauth_1/credential'
23
+
24
+ module Signet
25
+ module OAuth1
26
+ class Server
27
+
28
+ # @return [Proc] lookup the value from this Proc.
29
+ attr_accessor :nonce_timestamp, :client_credential, :token_credential,
30
+ :temporary_credential, :verifier
31
+
32
+ ##
33
+ # Creates an OAuth 1.0 server.
34
+ # @overload initialize(options)
35
+ # @param [Proc] nonce_timestamp verify a nonce/timestamp pair.
36
+ # @param [Proc] client_credential find a client credential.
37
+ # @param [Proc] token_credential find a token credential.
38
+ # @param [Proc] temporary_credential find a temporary credential.
39
+ # @param [Proc] verifier validate a verifier value.
40
+ #
41
+ # @example
42
+ # server = Signet::OAuth1::Server.new(
43
+ # :nonce_timestamp =>
44
+ # lambda { |n,t| OauthNonce.remember(n,t) },
45
+ # :client_credential =>
46
+ # lambda { |key| ClientCredential.find_by_key(key).to_hash },
47
+ # :token_credential =>
48
+ # lambda { |key| TokenCredential.find_by_key(key).to_hash },
49
+ # :temporary_credential =>
50
+ # lambda { |key| TemporaryCredential.find_by_key(key).to_hash },
51
+ # :verifier =>
52
+ # lambda {|verifier| Verifier.find_by_verifier(verifier).active? }
53
+ # )
54
+ def initialize(options={})
55
+ [:nonce_timestamp, :client_credential, :token_credential,
56
+ :temporary_credential, :verifier].each do |attr|
57
+ instance_variable_set("@#{attr}", options[attr])
58
+ end
59
+ end
60
+
61
+ ##
62
+ # Determine if the supplied nonce/timestamp pair is valid by calling
63
+ # the {#nonce_timestamp} Proc.
64
+ #
65
+ # @param [String, #to_str] nonce value from the request
66
+ # @param [String, #to_str] timestamp value from the request
67
+ # @return [Boolean] if the nonce/timestamp pair is valid.
68
+ def validate_nonce_timestamp(nonce, timestamp)
69
+ nonce =
70
+ @nonce_timestamp.call(nonce, timestamp) if
71
+ @nonce_timestamp.respond_to?(:call)
72
+ nonce ? true : false
73
+ end
74
+
75
+ ##
76
+ # Find the appropriate client credential by calling
77
+ # the {#client_credential} Proc.
78
+ #
79
+ # @param [String] key provided to the {#client_credential} Proc.
80
+ # @return [Signet::OAuth1::Credential] The client credential.
81
+ def find_client_credential(key)
82
+ call_credential_lookup(@client_credential, key)
83
+ end
84
+
85
+ ##
86
+ # Find the appropriate client credential by calling
87
+ # the {#token_credential} Proc.
88
+ #
89
+ # @param [String] key provided to the {#token_credential} Proc.
90
+ # @return [Signet::OAuth1::Credential] if the credential is found.
91
+ def find_token_credential(key)
92
+ call_credential_lookup(@token_credential, key)
93
+ end
94
+
95
+ ##
96
+ # Find the appropriate client credential by calling
97
+ # the {#temporary_credential} Proc.
98
+ #
99
+ # @param [String] key provided to the {#temporary_credential} Proc.
100
+ # @return [Signet::OAuth1::Credential] if the credential is found.
101
+ def find_temporary_credential(key)
102
+ call_credential_lookup(@temporary_credential, key)
103
+ end
104
+
105
+ ##
106
+ # Call a credential lookup, and cast the result to a proper Credential.
107
+ #
108
+ # @param [Proc] credential to call.
109
+ # @param [String] key provided to the Proc in <code>credential</code>
110
+ # @return [Signet::OAuth1::Credential] credential provided by
111
+ # <code>credential</code> (if any).
112
+ def call_credential_lookup(credential, key)
113
+ cred = credential.call(key) if
114
+ credential.respond_to?(:call)
115
+ return nil if cred.nil?
116
+ return nil unless (cred.respond_to?(:to_str) ||
117
+ cred.respond_to?(:to_ary) ||
118
+ cred.respond_to?(:to_hash) )
119
+ if(cred.instance_of?(::Signet::OAuth1::Credential))
120
+ cred
121
+ else
122
+ ::Signet::OAuth1::Credential.new(cred)
123
+ end
124
+ end
125
+
126
+ ##
127
+ # Determine if the verifier is valid by calling the Proc in {#verifier}.
128
+ #
129
+ # @param [String] Key provided to the {#verifier} Proc.
130
+ # @return [Boolean] if the verifier Proc returns anything other than
131
+ # <code>nil</code> or <code>false</code>.
132
+ def find_verifier(verifier)
133
+ verified = @verifier.call(verifier) if @verifier.respond_to?(:call)
134
+ verified ? true : false
135
+ end
136
+
137
+
138
+ ##
139
+ # Validate and normalize the components from an HTTP request.
140
+ # @overload verify_request_components(options)
141
+ # @param [Faraday::Request] request A pre-constructed request to verify.
142
+ # @param [String] method the HTTP method , defaults to <code>GET</code>
143
+ # @param [Addressable::URI, String] uri the URI .
144
+ # @param [Hash, Array] headers the HTTP headers.
145
+ # @param [StringIO, String] body The HTTP body.
146
+ # @param [HTTPAdapter] adapter The HTTP adapter(optional).
147
+ # @return [Hash] normalized request components
148
+ def verify_request_components(options={})
149
+ if options[:request]
150
+ if options[:request].kind_of?(Faraday::Request) || options[:request].kind_of?(Array)
151
+ request = options[:request]
152
+ elsif options[:adapter]
153
+ request = options[:adapter].adapt_request(options[:request])
154
+ end
155
+ method = request.method
156
+ uri = request.path
157
+ headers = request.headers
158
+ body = request.body
159
+ else
160
+ method = options[:method] || :get
161
+ uri = options[:uri]
162
+ headers = options[:headers] || []
163
+ body = options[:body] || ''
164
+ end
165
+
166
+ headers = headers.to_a if headers.kind_of?(Hash)
167
+ method = method.to_s.upcase
168
+
169
+ request_components = {
170
+ :method => method,
171
+ :uri => uri,
172
+ :headers => headers
173
+ }
174
+
175
+ # Verify that we have all the pieces required to validate the HTTP request
176
+ request_components.each do |(key, value)|
177
+ unless value
178
+ raise ArgumentError, "Missing :#{key} parameter."
179
+ end
180
+ end
181
+ request_components[:body] = body
182
+ request_components
183
+ end
184
+
185
+ ##
186
+ # Validate and normalize the HTTP Authorization header.
187
+ #
188
+ # @param [Array] headers from HTTP request.
189
+ # @return [Hash] Hash of Authorization header.
190
+ def verify_auth_header_components(headers)
191
+ auth_header = headers.find{|x| x[0] == 'Authorization'}
192
+ if(auth_header.nil? || auth_header[1] == '')
193
+ raise MalformedAuthorizationError.new('Authorization header is missing')
194
+ end
195
+ auth_hash = ::Signet::OAuth1.parse_authorization_header(
196
+ auth_header[1] ).inject({}) {|acc, (key,val)| acc[key.downcase] = val; acc}
197
+
198
+ auth_hash
199
+ end
200
+
201
+
202
+ ##
203
+ # @overload request_realm(options)
204
+ # @param [Hash] request A pre-constructed request to verify.
205
+ # @param [String] method the HTTP method , defaults to <code>GET</code>
206
+ # @param [Addressable::URI, String] uri the URI .
207
+ # @param [Hash, Array] headers the HTTP headers.
208
+ # @param [StringIO, String] body The HTTP body.
209
+ # @param [HTTPAdapter] adapter The HTTP adapter(optional).
210
+ # @return [String] The Authorization realm(see RFC 2617) of the request.
211
+ def request_realm(options={})
212
+ if(options[:request])
213
+ request_components = verify_request_components(
214
+ :request=>options[:request],
215
+ :adapter=>options[:adapter] )
216
+ else
217
+ request_components = verify_request_components(
218
+ :method=>options[:method],
219
+ :uri=>options[:uri],
220
+ :headers=>options[:headers],
221
+ :body=>options[:body] )
222
+ end
223
+
224
+ auth_header = request_components[:headers].find{|x| x[0] == 'Authorization'}
225
+ if(auth_header.nil? || auth_header[1] == '')
226
+ raise MalformedAuthorizationError.new('Authorization header is missing')
227
+ end
228
+ auth_hash = ::Signet::OAuth1.parse_authorization_header(
229
+ auth_header[1] ).inject({}) {|acc, (key,val)| acc[key.downcase] = val; acc}
230
+ auth_hash['realm']
231
+ end
232
+
233
+ ##
234
+ # Authenticates a temporary credential request. If no oauth_callback is
235
+ # present in the request, <code>oob</code> will be returned.
236
+ #
237
+ # @overload authenticate_temporary_credential_request(options)
238
+ # @param [Hash] request The configuration parameters for the request.
239
+ # @param [String] method the HTTP method , defaults to <code>GET</code>
240
+ # @param [Addressable::URI, String] uri the URI .
241
+ # @param [Hash, Array] headers the HTTP headers.
242
+ # @param [StringIO, String] body The HTTP body.
243
+ # @param [HTTPAdapter] adapter The HTTP adapter(optional).
244
+ # @return [String] The oauth_callback value, or <code>false</code> if not valid.
245
+ def authenticate_temporary_credential_request(options={})
246
+ verifications = {
247
+ :client_credential =>
248
+ lambda { |x| ::Signet::OAuth1::Credential.new('Client credential key',
249
+ 'Client credential secret'
250
+ )
251
+ }
252
+ }
253
+ verifications.each do |(key, value)|
254
+ raise ArgumentError, "#{key} was not set." unless self.send(key)
255
+ end
256
+
257
+ if(options[:request])
258
+ request_components = verify_request_components(
259
+ :request=>options[:request],
260
+ :adapter=>options[:adapter] )
261
+ else
262
+ request_components = verify_request_components(
263
+ :method=>options[:method],
264
+ :uri=>options[:uri],
265
+ :headers=>options[:headers] )
266
+ end
267
+ # body should be blank; we don't care in any case.
268
+ method = request_components[:method]
269
+ uri = request_components[:uri]
270
+ headers = request_components[:headers]
271
+
272
+ auth_hash = verify_auth_header_components(headers)
273
+ return false unless(client_credential = find_client_credential(
274
+ auth_hash['oauth_consumer_key']) )
275
+
276
+ return false unless validate_nonce_timestamp(auth_hash['oauth_nonce'],
277
+ auth_hash['oauth_timestamp'])
278
+ client_credential_secret = client_credential.secret if client_credential
279
+
280
+ computed_signature = ::Signet::OAuth1.sign_parameters(
281
+ method,
282
+ uri,
283
+ # Realm isn't used, and will throw the signature off.
284
+ auth_hash.reject{|k,v| k=='realm'}.to_a,
285
+ client_credential_secret,
286
+ nil
287
+ )
288
+ if(computed_signature == auth_hash['oauth_signature'])
289
+ if(auth_hash.fetch('oauth_callback', 'oob').empty?)
290
+ 'oob'
291
+ else
292
+ auth_hash.fetch('oauth_callback')
293
+ end
294
+ else
295
+ false
296
+ end
297
+ end
298
+
299
+
300
+ ##
301
+ # Authenticates a token credential request.
302
+ # @overload authenticate_token_credential_request(options)
303
+ # @param [Hash] request The configuration parameters for the request.
304
+ # @param [String] method the HTTP method , defaults to <code>GET</code>
305
+ # @param [Addressable::URI, String] uri the URI .
306
+ # @param [Hash, Array] headers the HTTP headers.
307
+ # @param [StringIO, String] body The HTTP body.
308
+ # @param [HTTPAdapter] adapter The HTTP adapter(optional).
309
+ # @return [Hash] A hash of credentials and realm for a valid request,
310
+ # or <code>nil</code> if not valid.
311
+ def authenticate_token_credential_request(options={})
312
+ verifications = {
313
+ :client_credential =>
314
+ lambda {|x| ::Signet::OAuth1::Credential.new('Client credential key',
315
+ 'Client credential secret')
316
+ },
317
+ :temporary_credential =>
318
+ lambda {|x| ::Signet::OAuth1::Credential.new('Temporary credential key',
319
+ 'Temporary credential secret')
320
+ },
321
+ :verifier =>
322
+ lambda {|x| 'Verifier' }
323
+ }
324
+ verifications.each do |(key, value)|
325
+ unless self.send(key)
326
+ raise ArgumentError, "#{key} was not set."
327
+ end
328
+ end
329
+ if(options[:request])
330
+ request_components = verify_request_components(
331
+ :request=>options[:request],
332
+ :adapter=>options[:adapter]
333
+ )
334
+ else
335
+ request_components = verify_request_components(
336
+ :method=>options[:method],
337
+ :uri=>options[:uri],
338
+ :headers=>options[:headers],
339
+ :body=>options[:body]
340
+ )
341
+ end
342
+ # body should be blank; we don't care in any case.
343
+ method = request_components[:method]
344
+ uri = request_components[:uri]
345
+ headers = request_components[:headers]
346
+
347
+ auth_hash = verify_auth_header_components(headers)
348
+ return false unless(
349
+ client_credential = find_client_credential(auth_hash['oauth_consumer_key'])
350
+ )
351
+ return false unless(
352
+ temporary_credential = find_temporary_credential(auth_hash['oauth_token'])
353
+ )
354
+ return false unless validate_nonce_timestamp(
355
+ auth_hash['oauth_nonce'], auth_hash['oauth_timestamp'])
356
+
357
+ computed_signature = ::Signet::OAuth1.sign_parameters(
358
+ method,
359
+ uri,
360
+ # Realm isn't used, and will throw the signature off.
361
+ auth_hash.reject{|k,v| k=='realm'}.to_a,
362
+ client_credential.secret,
363
+ temporary_credential.secret
364
+ )
365
+
366
+ if(computed_signature == auth_hash['oauth_signature'])
367
+ {:client_credential=>client_credential,
368
+ :temporary_credential=>temporary_credential,
369
+ :realm=>auth_hash['realm']
370
+ }
371
+ else
372
+ nil
373
+ end
374
+ end
375
+
376
+ ##
377
+ # Authenticates a request for a protected resource.
378
+ # @overload authenticate_resource_request(options)
379
+ # @param [Hash] request The configuration parameters for the request.
380
+ # @param [String] method the HTTP method , defaults to <code>GET</code>
381
+ # @param [Addressable::URI, String] uri the URI .
382
+ # @param [Hash, Array] headers the HTTP headers.
383
+ # @param [StringIO, String] body The HTTP body.
384
+ # @param [Boolean] two_legged skip the token_credential lookup?
385
+ # @param [HTTPAdapter] adapter The HTTP adapter(optional).
386
+ #
387
+ # @return [Hash] A hash of the credentials and realm for a valid request,
388
+ # or <code>nil</code> if not valid.
389
+ def authenticate_resource_request(options={})
390
+ verifications = {
391
+ :client_credential =>
392
+ lambda do |x|
393
+ ::Signet::OAuth1::Credential.new('Client credential key',
394
+ 'Client credential secret')
395
+ end
396
+ }
397
+
398
+ unless(options[:two_legged] == true)
399
+ verifications.update(
400
+ :token_credential =>
401
+ lambda do |x|
402
+ ::Signet::OAuth1::Credential.new('Token credential key',
403
+ 'Token credential secret')
404
+ end
405
+ )
406
+ end
407
+ # Make sure all required state is set
408
+ verifications.each do |(key, value)|
409
+ unless self.send(key)
410
+ raise ArgumentError, "#{key} was not set."
411
+ end
412
+ end
413
+
414
+ if(options[:request])
415
+ request_components = verify_request_components(
416
+ :request=>options[:request],
417
+ :adapter=>options[:adapter] )
418
+ else
419
+ request_components = verify_request_components(
420
+ :method=>options[:method],
421
+ :uri=>options[:uri],
422
+ :headers=>options[:headers],
423
+ :body=>options[:body] )
424
+ end
425
+ method = request_components[:method]
426
+ uri = request_components[:uri]
427
+ headers = request_components[:headers]
428
+ body = request_components[:body]
429
+
430
+
431
+ if !body.kind_of?(String) && body.respond_to?(:each)
432
+ # Just in case we get a chunked body
433
+ merged_body = StringIO.new
434
+ body.each do |chunk|
435
+ merged_body.write(chunk)
436
+ end
437
+ body = merged_body.string
438
+ end
439
+ if !body.kind_of?(String)
440
+ raise TypeError, "Expected String, got #{body.class}."
441
+ end
442
+
443
+ media_type = nil
444
+ headers.each do |(header, value)|
445
+ if header.downcase == 'Content-Type'.downcase
446
+ media_type = value.gsub(/^([^;]+)(;.*?)?$/, '\1')
447
+ end
448
+ end
449
+
450
+ auth_hash = verify_auth_header_components(headers)
451
+
452
+ auth_token = auth_hash['oauth_token']
453
+
454
+
455
+ unless(options[:two_legged])
456
+ return nil if(auth_token.nil?)
457
+ return nil unless(token_credential = find_token_credential(auth_token))
458
+ token_credential_secret = token_credential.secret if token_credential
459
+ end
460
+
461
+ return nil unless(client_credential =
462
+ find_client_credential(auth_hash['oauth_consumer_key']))
463
+
464
+ return nil unless validate_nonce_timestamp(auth_hash['oauth_nonce'],
465
+ auth_hash['oauth_timestamp'])
466
+
467
+ if(method == ('POST' || 'PUT') &&
468
+ media_type == 'application/x-www-form-urlencoded')
469
+ request_components[:body] = body
470
+ post_parameters = Addressable::URI.form_unencode(body)
471
+ post_parameters.each {|param| param[1] = "" if param[1].nil?}
472
+ # If the auth header doesn't have the same params as the body, it
473
+ # can't have been signed correctly(5849#3.4.1.3)
474
+ unless(post_parameters.sort == auth_hash.reject{|k,v| k.index('oauth_')}.to_a.sort)
475
+ raise MalformedAuthorizationError.new(
476
+ 'Request is of type application/x-www-form-urlencoded ' +
477
+ 'but Authentication header did not include form values'
478
+ )
479
+ end
480
+ end
481
+
482
+ client_credential_secret = client_credential.secret if client_credential
483
+
484
+ computed_signature = ::Signet::OAuth1.sign_parameters(
485
+ method,
486
+ uri,
487
+ # Realm isn't used, and will throw the signature off.
488
+ auth_hash.reject{|k,v| k=='realm'}.to_a,
489
+ client_credential_secret,
490
+ token_credential_secret
491
+ )
492
+
493
+ if(computed_signature == auth_hash['oauth_signature'])
494
+ {:client_credential=>client_credential,
495
+ :token_credential=>token_credential,
496
+ :realm=>auth_hash['realm']
497
+ }
498
+ else
499
+ nil
500
+ end
501
+ end
502
+
503
+ end
504
+ end
505
+ end