oauth_weshays 0.4.8.pre

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.
Files changed (89) hide show
  1. checksums.yaml +15 -0
  2. data/.gemtest +0 -0
  3. data/Gemfile +16 -0
  4. data/Gemfile.lock +47 -0
  5. data/HISTORY +173 -0
  6. data/LICENSE +20 -0
  7. data/README.rdoc +75 -0
  8. data/Rakefile +37 -0
  9. data/TODO +32 -0
  10. data/bin/oauth +5 -0
  11. data/examples/yql.rb +44 -0
  12. data/lib/digest/hmac.rb +104 -0
  13. data/lib/oauth/cli.rb +378 -0
  14. data/lib/oauth/client/action_controller_request.rb +65 -0
  15. data/lib/oauth/client/em_http.rb +120 -0
  16. data/lib/oauth/client/helper.rb +91 -0
  17. data/lib/oauth/client/net_http.rb +120 -0
  18. data/lib/oauth/client.rb +4 -0
  19. data/lib/oauth/consumer.rb +389 -0
  20. data/lib/oauth/core_ext.rb +31 -0
  21. data/lib/oauth/errors/error.rb +4 -0
  22. data/lib/oauth/errors/problem.rb +14 -0
  23. data/lib/oauth/errors/unauthorized.rb +12 -0
  24. data/lib/oauth/errors.rb +3 -0
  25. data/lib/oauth/helper.rb +109 -0
  26. data/lib/oauth/oauth.rb +13 -0
  27. data/lib/oauth/oauth_test_helper.rb +25 -0
  28. data/lib/oauth/request_proxy/action_controller_request.rb +62 -0
  29. data/lib/oauth/request_proxy/base.rb +174 -0
  30. data/lib/oauth/request_proxy/curb_request.rb +55 -0
  31. data/lib/oauth/request_proxy/em_http_request.rb +66 -0
  32. data/lib/oauth/request_proxy/jabber_request.rb +41 -0
  33. data/lib/oauth/request_proxy/mock_request.rb +44 -0
  34. data/lib/oauth/request_proxy/net_http.rb +73 -0
  35. data/lib/oauth/request_proxy/rack_request.rb +44 -0
  36. data/lib/oauth/request_proxy/typhoeus_request.rb +53 -0
  37. data/lib/oauth/request_proxy.rb +24 -0
  38. data/lib/oauth/server.rb +66 -0
  39. data/lib/oauth/signature/base.rb +110 -0
  40. data/lib/oauth/signature/hmac/base.rb +15 -0
  41. data/lib/oauth/signature/hmac/md5.rb +8 -0
  42. data/lib/oauth/signature/hmac/rmd160.rb +8 -0
  43. data/lib/oauth/signature/hmac/sha1.rb +9 -0
  44. data/lib/oauth/signature/hmac/sha2.rb +8 -0
  45. data/lib/oauth/signature/md5.rb +13 -0
  46. data/lib/oauth/signature/plaintext.rb +23 -0
  47. data/lib/oauth/signature/rsa/sha1.rb +46 -0
  48. data/lib/oauth/signature/sha1.rb +13 -0
  49. data/lib/oauth/signature.rb +45 -0
  50. data/lib/oauth/token.rb +7 -0
  51. data/lib/oauth/tokens/access_token.rb +71 -0
  52. data/lib/oauth/tokens/consumer_token.rb +33 -0
  53. data/lib/oauth/tokens/request_token.rb +32 -0
  54. data/lib/oauth/tokens/server_token.rb +9 -0
  55. data/lib/oauth/tokens/token.rb +17 -0
  56. data/lib/oauth.rb +13 -0
  57. data/oauth.gemspec +148 -0
  58. data/tasks/deployment.rake +34 -0
  59. data/tasks/environment.rake +7 -0
  60. data/tasks/website.rake +17 -0
  61. data/test/cases/oauth_case.rb +19 -0
  62. data/test/cases/spec/1_0-final/test_construct_request_url.rb +62 -0
  63. data/test/cases/spec/1_0-final/test_normalize_request_parameters.rb +88 -0
  64. data/test/cases/spec/1_0-final/test_parameter_encodings.rb +86 -0
  65. data/test/cases/spec/1_0-final/test_signature_base_strings.rb +77 -0
  66. data/test/integration/consumer_test.rb +307 -0
  67. data/test/keys/rsa.cert +11 -0
  68. data/test/keys/rsa.pem +16 -0
  69. data/test/test_access_token.rb +26 -0
  70. data/test/test_action_controller_request_proxy.rb +133 -0
  71. data/test/test_consumer.rb +220 -0
  72. data/test/test_curb_request_proxy.rb +77 -0
  73. data/test/test_em_http_client.rb +80 -0
  74. data/test/test_em_http_request_proxy.rb +115 -0
  75. data/test/test_helper.rb +28 -0
  76. data/test/test_hmac_sha1.rb +20 -0
  77. data/test/test_net_http_client.rb +292 -0
  78. data/test/test_net_http_request_proxy.rb +72 -0
  79. data/test/test_oauth_helper.rb +94 -0
  80. data/test/test_rack_request_proxy.rb +40 -0
  81. data/test/test_request_token.rb +51 -0
  82. data/test/test_rsa_sha1.rb +59 -0
  83. data/test/test_server.rb +40 -0
  84. data/test/test_signature.rb +22 -0
  85. data/test/test_signature_base.rb +32 -0
  86. data/test/test_signature_plain_text.rb +31 -0
  87. data/test/test_token.rb +14 -0
  88. data/test/test_typhoeus_request_proxy.rb +80 -0
  89. metadata +252 -0
@@ -0,0 +1,120 @@
1
+ require 'oauth/helper'
2
+ require 'oauth/client/helper'
3
+ require 'oauth/request_proxy/net_http'
4
+
5
+ class Net::HTTPGenericRequest
6
+ include OAuth::Helper
7
+
8
+ attr_reader :oauth_helper
9
+
10
+ # Add the OAuth information to an HTTP request. Depending on the <tt>options[:scheme]</tt> setting
11
+ # this may add a header, additional query string parameters, or additional POST body parameters.
12
+ # The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
13
+ # header.
14
+ #
15
+ # * http - Configured Net::HTTP instance
16
+ # * consumer - OAuth::Consumer instance
17
+ # * token - OAuth::Token instance
18
+ # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
19
+ # +signature_method+, +nonce+, +timestamp+)
20
+ #
21
+ # This method also modifies the <tt>User-Agent</tt> header to add the OAuth gem version.
22
+ #
23
+ # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1],
24
+ # {OAuth Request Body Hash 1.0 Draft 4}[http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/4/spec.html]
25
+ def oauth!(http, consumer = nil, token = nil, options = {})
26
+ helper_options = oauth_helper_options(http, consumer, token, options)
27
+ @oauth_helper = OAuth::Client::Helper.new(self, helper_options)
28
+ @oauth_helper.amend_user_agent_header(self)
29
+ @oauth_helper.hash_body if oauth_body_hash_required?
30
+ self.send("set_oauth_#{helper_options[:scheme]}")
31
+ end
32
+
33
+ # Create a string suitable for signing for an HTTP request. This process involves parameter
34
+ # normalization as specified in the OAuth specification. The exact normalization also depends
35
+ # on the <tt>options[:scheme]</tt> being used so this must match what will be used for the request
36
+ # itself. The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
37
+ # header.
38
+ #
39
+ # * http - Configured Net::HTTP instance
40
+ # * consumer - OAuth::Consumer instance
41
+ # * token - OAuth::Token instance
42
+ # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
43
+ # +signature_method+, +nonce+, +timestamp+)
44
+ #
45
+ # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1],
46
+ # {OAuth Request Body Hash 1.0 Draft 4}[http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/4/spec.html]
47
+ def signature_base_string(http, consumer = nil, token = nil, options = {})
48
+ helper_options = oauth_helper_options(http, consumer, token, options)
49
+ oauth_helper = OAuth::Client::Helper.new(self, helper_options)
50
+ oauth_helper.hash_body if oauth_body_hash_required?
51
+ oauth_helper.signature_base_string
52
+ end
53
+
54
+ private
55
+
56
+ def oauth_helper_options(http, consumer, token, options)
57
+ { :request_uri => oauth_full_request_uri(http,options),
58
+ :consumer => consumer,
59
+ :token => token,
60
+ :scheme => 'header',
61
+ :signature_method => nil,
62
+ :nonce => nil,
63
+ :timestamp => nil }.merge(options)
64
+ end
65
+
66
+ def oauth_full_request_uri(http,options)
67
+ uri = URI.parse(self.path)
68
+ uri.host = http.address
69
+ uri.port = http.port
70
+
71
+ if options[:request_endpoint] && options[:site]
72
+ is_https = options[:site].match(%r(^https://))
73
+ uri.host = options[:site].gsub(%r(^https?://), '')
74
+ uri.port ||= is_https ? 443 : 80
75
+ end
76
+
77
+ if http.respond_to?(:use_ssl?) && http.use_ssl?
78
+ uri.scheme = "https"
79
+ else
80
+ uri.scheme = "http"
81
+ end
82
+
83
+ uri.to_s
84
+ end
85
+
86
+ def oauth_body_hash_required?
87
+ request_body_permitted? && !content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded")
88
+ end
89
+
90
+ def set_oauth_header
91
+ self['Authorization'] = @oauth_helper.header
92
+ end
93
+
94
+ # FIXME: if you're using a POST body and query string parameters, this method
95
+ # will move query string parameters into the body unexpectedly. This may
96
+ # cause problems with non-x-www-form-urlencoded bodies submitted to URLs
97
+ # containing query string params. If duplicate parameters are present in both
98
+ # places, all instances should be included when calculating the signature
99
+ # base string.
100
+
101
+ def set_oauth_body
102
+ self.set_form_data(@oauth_helper.stringify_keys(@oauth_helper.parameters_with_oauth))
103
+ params_with_sig = @oauth_helper.parameters.merge(:oauth_signature => @oauth_helper.signature)
104
+ self.set_form_data(@oauth_helper.stringify_keys(params_with_sig))
105
+ end
106
+
107
+ def set_oauth_query_string
108
+ oauth_params_str = @oauth_helper.oauth_parameters.map { |k,v| [escape(k), escape(v)] * "=" }.join("&")
109
+ uri = URI.parse(path)
110
+ if uri.query.to_s == ""
111
+ uri.query = oauth_params_str
112
+ else
113
+ uri.query = uri.query + "&" + oauth_params_str
114
+ end
115
+
116
+ @path = uri.to_s
117
+
118
+ @path << "&oauth_signature=#{escape(oauth_helper.signature)}"
119
+ end
120
+ end
@@ -0,0 +1,4 @@
1
+ module OAuth
2
+ module Client
3
+ end
4
+ end
@@ -0,0 +1,389 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'oauth/oauth'
4
+ require 'oauth/client/net_http'
5
+ require 'oauth/errors'
6
+ require 'cgi'
7
+
8
+ module OAuth
9
+ class Consumer
10
+ # determine the certificate authority path to verify SSL certs
11
+ CA_FILES = %w(/etc/ssl/certs/ca-certificates.crt /usr/share/curl/curl-ca-bundle.crt)
12
+ CA_FILES.each do |ca_file|
13
+ if File.exists?(ca_file)
14
+ CA_FILE = ca_file
15
+ break
16
+ end
17
+ end
18
+ CA_FILE = nil unless defined?(CA_FILE)
19
+
20
+ @@default_options = {
21
+ # Signature method used by server. Defaults to HMAC-SHA1
22
+ :signature_method => 'HMAC-SHA1',
23
+
24
+ # default paths on site. These are the same as the defaults set up by the generators
25
+ :request_token_path => '/oauth/request_token',
26
+ :authorize_path => '/oauth/authorize',
27
+ :access_token_path => '/oauth/access_token',
28
+
29
+ :proxy => nil,
30
+ # How do we send the oauth values to the server see
31
+ # http://oauth.net/core/1.0/#consumer_req_param for more info
32
+ #
33
+ # Possible values:
34
+ #
35
+ # :header - via the Authorize header (Default) ( option 1. in spec)
36
+ # :body - url form encoded in body of POST request ( option 2. in spec)
37
+ # :query_string - via the query part of the url ( option 3. in spec)
38
+ :scheme => :header,
39
+
40
+ # Default http method used for OAuth Token Requests (defaults to :post)
41
+ :http_method => :post,
42
+
43
+ # Add a custom ca_file for consumer
44
+ # :ca_file => '/etc/certs.pem'
45
+
46
+ :oauth_version => "1.0"
47
+ }
48
+
49
+ attr_accessor :options, :key, :secret
50
+ attr_writer :site, :http
51
+
52
+ # Create a new consumer instance by passing it a configuration hash:
53
+ #
54
+ # @consumer = OAuth::Consumer.new(key, secret, {
55
+ # :site => "http://term.ie",
56
+ # :scheme => :header,
57
+ # :http_method => :post,
58
+ # :request_token_path => "/oauth/example/request_token.php",
59
+ # :access_token_path => "/oauth/example/access_token.php",
60
+ # :authorize_path => "/oauth/example/authorize.php"
61
+ # })
62
+ #
63
+ # Start the process by requesting a token
64
+ #
65
+ # @request_token = @consumer.get_request_token
66
+ # session[:request_token] = @request_token
67
+ # redirect_to @request_token.authorize_url
68
+ #
69
+ # When user returns create an access_token
70
+ #
71
+ # @access_token = @request_token.get_access_token
72
+ # @photos=@access_token.get('/photos.xml')
73
+ #
74
+ def initialize(consumer_key, consumer_secret, options = {})
75
+ @key = consumer_key
76
+ @secret = consumer_secret
77
+
78
+ # ensure that keys are symbols
79
+ @options = @@default_options.merge(options.inject({}) do |opts, (key, value)|
80
+ opts[key.to_sym] = value
81
+ opts
82
+ end)
83
+ end
84
+
85
+ # The default http method
86
+ def http_method
87
+ @http_method ||= @options[:http_method] || :post
88
+ end
89
+
90
+ # The HTTP object for the site. The HTTP Object is what you get when you do Net::HTTP.new
91
+ def http
92
+ @http ||= create_http
93
+ end
94
+
95
+ # Contains the root URI for this site
96
+ def uri(custom_uri = nil)
97
+ if custom_uri
98
+ @uri = custom_uri
99
+ @http = create_http # yike, oh well. less intrusive this way
100
+ else # if no custom passed, we use existing, which, if unset, is set to site uri
101
+ @uri ||= URI.parse(site)
102
+ end
103
+ end
104
+
105
+ def get_access_token(request_token, request_options = {}, *arguments, &block)
106
+ response = token_request(http_method, (access_token_url? ? access_token_url : access_token_path), request_token, request_options, *arguments, &block)
107
+ OAuth::AccessToken.from_hash(self, response)
108
+ end
109
+
110
+ # Makes a request to the service for a new OAuth::RequestToken
111
+ #
112
+ # @request_token = @consumer.get_request_token
113
+ #
114
+ # To include OAuth parameters:
115
+ #
116
+ # @request_token = @consumer.get_request_token \
117
+ # :oauth_callback => "http://example.com/cb"
118
+ #
119
+ # To include application-specific parameters:
120
+ #
121
+ # @request_token = @consumer.get_request_token({}, :foo => "bar")
122
+ #
123
+ # TODO oauth_callback should be a mandatory parameter
124
+ def get_request_token(request_options = {}, *arguments, &block)
125
+ # if oauth_callback wasn't provided, it is assumed that oauth_verifiers
126
+ # will be exchanged out of band
127
+ request_options[:oauth_callback] ||= OAuth::OUT_OF_BAND unless request_options[:exclude_callback]
128
+
129
+ if block_given?
130
+ response = token_request(http_method,
131
+ (request_token_url? ? request_token_url : request_token_path),
132
+ nil,
133
+ request_options,
134
+ *arguments, &block)
135
+ else
136
+ response = token_request(http_method, (request_token_url? ? request_token_url : request_token_path), nil, request_options, *arguments)
137
+ end
138
+ OAuth::RequestToken.from_hash(self, response)
139
+ end
140
+
141
+ # Creates, signs and performs an http request.
142
+ # It's recommended to use the OAuth::Token classes to set this up correctly.
143
+ # request_options take precedence over consumer-wide options when signing
144
+ # a request.
145
+ # arguments are POST and PUT bodies (a Hash, string-encoded parameters, or
146
+ # absent), followed by additional HTTP headers.
147
+ #
148
+ # @consumer.request(:get, '/people', @token, { :scheme => :query_string })
149
+ # @consumer.request(:post, '/people', @token, {}, @person.to_xml, { 'Content-Type' => 'application/xml' })
150
+ #
151
+ def request(http_method, path, token = nil, request_options = {}, *arguments)
152
+ if path !~ /^\//
153
+ @http = create_http(path)
154
+ _uri = URI.parse(path)
155
+ path = "#{_uri.path}#{_uri.query ? "?#{_uri.query}" : ""}"
156
+ end
157
+
158
+ # override the request with your own, this is useful for file uploads which Net::HTTP does not do
159
+ req = create_signed_request(http_method, path, token, request_options, *arguments)
160
+ return nil if block_given? and yield(req) == :done
161
+ rsp = http.request(req)
162
+ # check for an error reported by the Problem Reporting extension
163
+ # (http://wiki.oauth.net/ProblemReporting)
164
+ # note: a 200 may actually be an error; check for an oauth_problem key to be sure
165
+ if !(headers = rsp.to_hash["www-authenticate"]).nil? &&
166
+ (h = headers.select { |hdr| hdr =~ /^OAuth / }).any? &&
167
+ h.first =~ /oauth_problem/
168
+
169
+ # puts "Header: #{h.first}"
170
+
171
+ # TODO doesn't handle broken responses from api.login.yahoo.com
172
+ # remove debug code when done
173
+ params = OAuth::Helper.parse_header(h.first)
174
+
175
+ # puts "Params: #{params.inspect}"
176
+ # puts "Body: #{rsp.body}"
177
+
178
+ raise OAuth::Problem.new(params.delete("oauth_problem"), rsp, params)
179
+ end
180
+
181
+ rsp
182
+ end
183
+
184
+ # Creates and signs an http request.
185
+ # It's recommended to use the Token classes to set this up correctly
186
+ def create_signed_request(http_method, path, token = nil, request_options = {}, *arguments)
187
+ request = create_http_request(http_method, path, *arguments)
188
+ sign!(request, token, request_options)
189
+ request
190
+ end
191
+
192
+ # Creates a request and parses the result as url_encoded. This is used internally for the RequestToken and AccessToken requests.
193
+ def token_request(http_method, path, token = nil, request_options = {}, *arguments)
194
+ response = request(http_method, path, token, request_options, *arguments)
195
+ case response.code.to_i
196
+
197
+ when (200..299)
198
+ if block_given?
199
+ yield response.body
200
+ else
201
+ # symbolize keys
202
+ # TODO this could be considered unexpected behavior; symbols or not?
203
+ # TODO this also drops subsequent values from multi-valued keys
204
+ CGI.parse(response.body).inject({}) do |h,(k,v)|
205
+ h[k.strip.to_sym] = v.first
206
+ h[k.strip] = v.first
207
+ h
208
+ end
209
+ end
210
+ when (300..399)
211
+ # this is a redirect
212
+ uri = URI.parse(response.header['location'])
213
+ response.error! if uri.path == path # careful of those infinite redirects
214
+ self.token_request(http_method, uri.path, token, request_options, arguments)
215
+ when (400..499)
216
+ raise OAuth::Unauthorized, response
217
+ else
218
+ response.error!
219
+ end
220
+ end
221
+
222
+ # Sign the Request object. Use this if you have an externally generated http request object you want to sign.
223
+ def sign!(request, token = nil, request_options = {})
224
+ request.oauth!(http, self, token, options.merge(request_options))
225
+ end
226
+
227
+ # Return the signature_base_string
228
+ def signature_base_string(request, token = nil, request_options = {})
229
+ request.signature_base_string(http, self, token, options.merge(request_options))
230
+ end
231
+
232
+ def site
233
+ @options[:site].to_s
234
+ end
235
+
236
+ def request_endpoint
237
+ return nil if @options[:request_endpoint].nil?
238
+ @options[:request_endpoint].to_s
239
+ end
240
+
241
+ def scheme
242
+ @options[:scheme]
243
+ end
244
+
245
+ def request_token_path
246
+ @options[:request_token_path]
247
+ end
248
+
249
+ def authorize_path
250
+ @options[:authorize_path]
251
+ end
252
+
253
+ def access_token_path
254
+ @options[:access_token_path]
255
+ end
256
+
257
+ # TODO this is ugly, rewrite
258
+ def request_token_url
259
+ @options[:request_token_url] || site + request_token_path
260
+ end
261
+
262
+ def request_token_url?
263
+ @options.has_key?(:request_token_url)
264
+ end
265
+
266
+ def authorize_url
267
+ @options[:authorize_url] || site + authorize_path
268
+ end
269
+
270
+ def authorize_url?
271
+ @options.has_key?(:authorize_url)
272
+ end
273
+
274
+ def access_token_url
275
+ @options[:access_token_url] || site + access_token_path
276
+ end
277
+
278
+ def access_token_url?
279
+ @options.has_key?(:access_token_url)
280
+ end
281
+
282
+ def proxy
283
+ @options[:proxy]
284
+ end
285
+
286
+ protected
287
+
288
+ # Instantiates the http object
289
+ def create_http(_url = nil)
290
+
291
+
292
+ if !request_endpoint.nil?
293
+ _url = request_endpoint
294
+ end
295
+
296
+
297
+ if _url.nil? || _url[0] =~ /^\//
298
+ our_uri = URI.parse(site)
299
+ else
300
+ our_uri = URI.parse(_url)
301
+ end
302
+
303
+
304
+ if proxy.nil?
305
+ http_object = Net::HTTP.new(our_uri.host, our_uri.port)
306
+ else
307
+ proxy_uri = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
308
+ http_object = Net::HTTP.new(our_uri.host, our_uri.port, proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
309
+ end
310
+
311
+ http_object.use_ssl = (our_uri.scheme == 'https')
312
+
313
+ if @options[:ca_file] || CA_FILE
314
+ http_object.ca_file = @options[:ca_file] || CA_FILE
315
+ http_object.verify_mode = OpenSSL::SSL::VERIFY_PEER
316
+ http_object.verify_depth = 5
317
+ else
318
+ http_object.verify_mode = OpenSSL::SSL::VERIFY_NONE
319
+ end
320
+
321
+ http_object.read_timeout = http_object.open_timeout = @options[:timeout] || 30
322
+ http_object.open_timeout = @options[:open_timeout] if @options[:open_timeout]
323
+
324
+ http_object
325
+ end
326
+
327
+ # create the http request object for a given http_method and path
328
+ def create_http_request(http_method, path, *arguments)
329
+ http_method = http_method.to_sym
330
+
331
+ if [:post, :put].include?(http_method)
332
+ data = arguments.shift
333
+ end
334
+
335
+ # if the base site contains a path, add it now
336
+ uri = URI.parse(site)
337
+ path = uri.path + path if uri.path && uri.path != '/'
338
+
339
+ headers = arguments.first.is_a?(Hash) ? arguments.shift : {}
340
+
341
+ case http_method
342
+ when :post
343
+ request = Net::HTTP::Post.new(path,headers)
344
+ request["Content-Length"] = '0' # Default to 0
345
+ when :put
346
+ request = Net::HTTP::Put.new(path,headers)
347
+ request["Content-Length"] = '0' # Default to 0
348
+ when :get
349
+ request = Net::HTTP::Get.new(path,headers)
350
+ when :delete
351
+ request = Net::HTTP::Delete.new(path,headers)
352
+ when :head
353
+ request = Net::HTTP::Head.new(path,headers)
354
+ else
355
+ raise ArgumentError, "Don't know how to handle http_method: :#{http_method.to_s}"
356
+ end
357
+
358
+ if data.is_a?(Hash)
359
+ request.body = OAuth::Helper.normalize(data)
360
+ request.content_type = 'application/x-www-form-urlencoded'
361
+ elsif data
362
+ if data.respond_to?(:read)
363
+ request.body_stream = data
364
+ if data.respond_to?(:length)
365
+ request["Content-Length"] = data.length.to_s
366
+ elsif data.respond_to?(:stat) && data.stat.respond_to?(:size)
367
+ request["Content-Length"] = data.stat.size.to_s
368
+ else
369
+ raise ArgumentError, "Don't know how to send a body_stream that doesn't respond to .length or .stat.size"
370
+ end
371
+ else
372
+ request.body = data.to_s
373
+ request["Content-Length"] = request.body.length.to_s
374
+ end
375
+ end
376
+
377
+ request
378
+ end
379
+
380
+ def marshal_dump(*args)
381
+ {:key => @key, :secret => @secret, :options => @options}
382
+ end
383
+
384
+ def marshal_load(data)
385
+ initialize(data[:key], data[:secret], data[:options])
386
+ end
387
+
388
+ end
389
+ end
@@ -0,0 +1,31 @@
1
+ # these are to backport methods from 1.8.7/1.9.1 to 1.8.6
2
+
3
+ class Object
4
+
5
+ unless method_defined?(:tap)
6
+ def tap
7
+ yield self
8
+ self
9
+ end
10
+ end
11
+
12
+ end
13
+
14
+ class String
15
+
16
+
17
+
18
+ unless method_defined?(:bytesize)
19
+ def bytesize
20
+ self.size
21
+ end
22
+ end
23
+
24
+ unless method_defined?(:bytes)
25
+ def bytes
26
+ require 'enumerator'
27
+ Enumerable::Enumerator.new(self, :each_byte)
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,4 @@
1
+ module OAuth
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ module OAuth
2
+ class Problem < OAuth::Unauthorized
3
+ attr_reader :problem, :params
4
+ def initialize(problem, request = nil, params = {})
5
+ super(request)
6
+ @problem = problem
7
+ @params = params
8
+ end
9
+
10
+ def to_s
11
+ problem
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module OAuth
2
+ class Unauthorized < OAuth::Error
3
+ attr_reader :request
4
+ def initialize(request = nil)
5
+ @request = request
6
+ end
7
+
8
+ def to_s
9
+ [request.code, request.message] * " "
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ require 'oauth/errors/error'
2
+ require 'oauth/errors/unauthorized'
3
+ require 'oauth/errors/problem'
@@ -0,0 +1,109 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module OAuth
5
+ module Helper
6
+ extend self
7
+
8
+ # Escape +value+ by URL encoding all non-reserved character.
9
+ #
10
+ # See Also: {OAuth core spec version 1.0, section 5.1}[http://oauth.net/core/1.0#rfc.section.5.1]
11
+ def escape(value)
12
+ URI::escape(value.to_s, OAuth::RESERVED_CHARACTERS)
13
+ rescue ArgumentError
14
+ URI::escape(value.to_s.force_encoding(Encoding::UTF_8), OAuth::RESERVED_CHARACTERS)
15
+ end
16
+
17
+ # Generate a random key of up to +size+ bytes. The value returned is Base64 encoded with non-word
18
+ # characters removed.
19
+ def generate_key(size=32)
20
+ Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/, '')
21
+ end
22
+
23
+ alias_method :generate_nonce, :generate_key
24
+
25
+ def generate_timestamp #:nodoc:
26
+ Time.now.to_i.to_s
27
+ end
28
+
29
+ # Normalize a +Hash+ of parameter values. Parameters are sorted by name, using lexicographical
30
+ # byte value ordering. If two or more parameters share the same name, they are sorted by their value.
31
+ # Parameters are concatenated in their sorted order into a single string. For each parameter, the name
32
+ # is separated from the corresponding value by an "=" character, even if the value is empty. Each
33
+ # name-value pair is separated by an "&" character.
34
+ #
35
+ # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1]
36
+ def normalize(params)
37
+ params.sort.map do |k, values|
38
+ if values.is_a?(Array)
39
+ # make sure the array has an element so we don't lose the key
40
+ values << nil if values.empty?
41
+ # multiple values were provided for a single key
42
+ values.sort.collect do |v|
43
+ [escape(k),escape(v)] * "="
44
+ end
45
+ elsif values.is_a?(Hash)
46
+ normalize_nested_query(values, k)
47
+ else
48
+ [escape(k),escape(values)] * "="
49
+ end
50
+ end * "&"
51
+ end
52
+
53
+ #Returns a string representation of the Hash like in URL query string
54
+ # build_nested_query({:level_1 => {:level_2 => ['value_1','value_2']}}, 'prefix'))
55
+ # #=> ["prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_1", "prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_2"]
56
+ def normalize_nested_query(value, prefix = nil)
57
+ case value
58
+ when Array
59
+ value.map do |v|
60
+ normalize_nested_query(v, "#{prefix}[]")
61
+ end.flatten.sort
62
+ when Hash
63
+ value.map do |k, v|
64
+ normalize_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
65
+ end.flatten.sort
66
+ else
67
+ [escape(prefix), escape(value)] * "="
68
+ end
69
+ end
70
+
71
+ # Parse an Authorization / WWW-Authenticate header into a hash. Takes care of unescaping and
72
+ # removing surrounding quotes. Raises a OAuth::Problem if the header is not parsable into a
73
+ # valid hash. Does not validate the keys or values.
74
+ #
75
+ # hash = parse_header(headers['Authorization'] || headers['WWW-Authenticate'])
76
+ # hash['oauth_timestamp']
77
+ # #=>"1234567890"
78
+ #
79
+ def parse_header(header)
80
+ # decompose
81
+ params = header[6,header.length].split(/[,=&]/)
82
+
83
+ # odd number of arguments - must be a malformed header.
84
+ raise OAuth::Problem.new("Invalid authorization header") if params.size % 2 != 0
85
+
86
+ params.map! do |v|
87
+ # strip and unescape
88
+ val = unescape(v.strip)
89
+ # strip quotes
90
+ val.sub(/^\"(.*)\"$/, '\1')
91
+ end
92
+
93
+ # convert into a Hash
94
+ Hash[*params.flatten]
95
+ end
96
+
97
+ def unescape(value)
98
+ URI.unescape(value.gsub('+', '%2B'))
99
+ end
100
+
101
+ def stringify_keys(hash)
102
+ new_h = {}
103
+ hash.each do |k, v|
104
+ new_h[k.to_s] = v.is_a?(Hash) ? stringify_keys(v) : v
105
+ end
106
+ new_h
107
+ end
108
+ end
109
+ end