class OAuth::Consumer
Consumer credentials and request configuration for OAuth 1.0 / 1.0a flows.
Includes {OAuth::AUTH_SANITIZER::FilteredAttributes} so inspect output redacts the consumer secret while leaving non-sensitive configuration visible.
Constants
- CA_FILE
- CA_FILES
Attributes
Public Class Methods
Create a new consumer instance by passing it a configuration hash:
@consumer = OAuth::Consumer.new(key, secret, { :site => "http://term.ie", :scheme => :header, :http_method => :post, :request_token_path => "/oauth/example/request_token.php", :access_token_path => "/oauth/example/access_token.php", :authorize_path => "/oauth/example/authorize.php", :body_hash_enabled => false })
Start the process by requesting a token
@request_token = @consumer.get_request_token session[:request_token] = @request_token redirect_to @request_token.authorize_url
When user returns create an access_token
@access_token = @request_token.get_access_token @photos=@access_token.get('/photos.xml')
# File lib/oauth/consumer.rb 128 def initialize(consumer_key, consumer_secret, options = {}) 129 @key = consumer_key 130 @secret = consumer_secret 131 132 # ensure that keys are symbols 133 snaky_options = SnakyHash::SymbolKeyed.new(options) 134 @options = @@default_options.merge(snaky_options) 135 end
Public Instance Methods
# File lib/oauth/consumer.rb 423 def access_token_path 424 @options[:access_token_path] 425 end
# File lib/oauth/consumer.rb 452 def access_token_url 453 @options[:access_token_url] || (site + access_token_path) 454 end
# File lib/oauth/consumer.rb 456 def access_token_url? 457 @options.key?(:access_token_url) 458 end
# File lib/oauth/consumer.rb 415 def authenticate_path 416 @options[:authenticate_path] 417 end
# File lib/oauth/consumer.rb 436 def authenticate_url 437 @options[:authenticate_url] || (site + authenticate_path) 438 end
# File lib/oauth/consumer.rb 440 def authenticate_url? 441 @options.key?(:authenticate_url) 442 end
Creates and signs an http request. It's recommended to use the Token classes to set this up correctly
# File lib/oauth/consumer.rb 296 def create_signed_request(http_method, path, token = nil, request_options = {}, *arguments) 297 request = create_http_request(http_method, path, *arguments) 298 sign!(request, token, request_options) 299 request 300 end
# File lib/oauth/consumer.rb 142 def debug_output 143 @debug_output ||= case @options[:debug_output] 144 when nil, false 145 when true 146 $stdout 147 else 148 @options[:debug_output] 149 end 150 end
Exchanges a verified Request Token for an Access Token.
OAuth 1.0 vs 1.0a:
-
1.0a requires including oauth_verifier (as returned by the Provider after user authorization) when performing this exchange in a 3‑legged flow.
-
1.0 flows did not include oauth_verifier.
Usage (3‑legged):
access_token = request_token.get_access_token(oauth_verifier: params[:oauth_verifier])
@param request_token [OAuth::RequestToken] The authorized request token @param request_options [Hash] OAuth or request options (include :oauth_verifier for 1.0a) @param arguments [Array] Optional POST body and headers @yield [response_body] If a block is given, yields the raw response body. @return [OAuth::AccessToken]
# File lib/oauth/consumer.rb 182 def get_access_token(request_token, request_options = {}, *arguments, &block) 183 response = token_request( 184 http_method, 185 (access_token_url? ? access_token_url : access_token_path), 186 request_token, 187 request_options, 188 *arguments, 189 &block 190 ) 191 OAuth::AccessToken.from_hash(self, response) 192 end
Makes a request to the service for a new OAuth::RequestToken
Example:
@request_token = @consumer.get_request_token
To include OAuth parameters:
@request_token = @consumer.get_request_token( oauth_callback: "http://example.com/cb" )
To include application-specific parameters:
@request_token = @consumer.get_request_token({}, foo: "bar")
OAuth 1.0 vs 1.0a:
-
In 1.0a, the
ConsumerSHOULD send oauth_callback when obtaining a request token (or explicitly use OUT_OF_BAND) and the Provider MUST include oauth_callback_confirmed=true in the response. -
This library defaults oauth_callback to OUT_OF_BAND (“oob”) when not provided, which works for both 1.0 and 1.0a, and mirrors common provider behavior.
-
The oauth_callback_confirmed response is parsed by the token classes; it is not part of the signature base string and thus is not signed.
TODO: In a future major release, oauth_callback may be made mandatory unless
request_options[:exclude_callback] is set, to reflect 1.0a guidance.
@param request_options [Hash] OAuth options for the request. Notably
:oauth_callback can be set to a URL, or OAuth::OUT_OF_BAND ("oob").
@param arguments [Array] Optional POST body and headers @yield [response_body] If a block is given, yields the raw response body. @return [OAuth::RequestToken]
# File lib/oauth/consumer.rb 224 def get_request_token(request_options = {}, *arguments, &block) 225 # if oauth_callback wasn't provided, it is assumed that oauth_verifiers 226 # will be exchanged out of band 227 request_options[:oauth_callback] ||= OAuth::OUT_OF_BAND unless request_options[:exclude_callback] 228 229 response = if block 230 token_request( 231 http_method, 232 (request_token_url? ? request_token_url : request_token_path), 233 nil, 234 request_options, 235 *arguments, 236 &block 237 ) 238 else 239 token_request( 240 http_method, 241 (request_token_url? ? request_token_url : request_token_path), 242 nil, 243 request_options, 244 *arguments 245 ) 246 end 247 OAuth::RequestToken.from_hash(self, response) 248 end
The HTTP object for the site. The HTTP Object is what you get when you do Net::HTTP.new
# File lib/oauth/consumer.rb 153 def http 154 @http ||= create_http 155 end
The default http method
# File lib/oauth/consumer.rb 138 def http_method 139 @http_method ||= @options[:http_method] || :post 140 end
# File lib/oauth/consumer.rb 460 def proxy 461 @options[:proxy] 462 end
Creates, signs and performs an http request. It's recommended to use the OAuth::Token classes to set this up correctly. request_options take precedence over consumer-wide options when signing
a request.
arguments are POST and PUT bodies (a Hash, string-encoded parameters, or
absent), followed by additional HTTP headers.
@consumer.request(:get, '/people', @token, { :scheme => :query_string })
@consumer.request(:post, '/people', @token, {}, @person.to_xml, { 'Content-Type' => 'application/xml' })
# File lib/oauth/consumer.rb 260 def request(http_method, path, token = nil, request_options = {}, *arguments) 261 unless %r{^/} =~ path 262 @http = create_http(path) 263 uri = URI.parse(path) 264 path = "#{uri.path}#{"?#{uri.query}" if uri.query}" 265 end 266 267 # override the request with your own, this is useful for file uploads which Net::HTTP does not do 268 req = create_signed_request(http_method, path, token, request_options, *arguments) 269 return if block_given? && (yield(req) == :done) 270 271 rsp = http.request(req) 272 # check for an error reported by the Problem Reporting extension 273 # (https://wiki.oauth.net/ProblemReporting) 274 # note: a 200 may actually be an error; check for an oauth_problem key to be sure 275 if !(headers = rsp.to_hash["www-authenticate"]).nil? && 276 (h = headers.grep(/^OAuth /)).any? && 277 h.first.include?("oauth_problem") 278 279 # puts "Header: #{h.first}" 280 281 # TODO: doesn't handle broken responses from api.login.yahoo.com 282 # remove debug code when done 283 params = OAuth::Helper.parse_header(h.first) 284 285 # puts "Params: #{params.inspect}" 286 # puts "Body: #{rsp.body}" 287 288 raise OAuth::Problem.new(params.delete("oauth_problem"), rsp, params) 289 end 290 291 rsp 292 end
# File lib/oauth/consumer.rb 401 def request_endpoint 402 return if @options[:request_endpoint].nil? 403 404 @options[:request_endpoint].to_s 405 end
# File lib/oauth/consumer.rb 411 def request_token_path 412 @options[:request_token_path] 413 end
TODO: this is ugly, rewrite
# File lib/oauth/consumer.rb 428 def request_token_url 429 @options[:request_token_url] || (site + request_token_path) 430 end
# File lib/oauth/consumer.rb 432 def request_token_url? 433 @options.key?(:request_token_url) 434 end
# File lib/oauth/consumer.rb 407 def scheme 408 @options[:scheme] 409 end
Sign the Request object. Use this if you have an externally generated http request object you want to sign.
# File lib/oauth/consumer.rb 388 def sign!(request, token = nil, request_options = {}) 389 request.oauth!(http, self, token, options.merge(request_options)) 390 end
Return the signature_base_string
# File lib/oauth/consumer.rb 393 def signature_base_string(request, token = nil, request_options = {}) 394 request.signature_base_string(http, self, token, options.merge(request_options)) 395 end
# File lib/oauth/consumer.rb 397 def site 398 @options[:site].to_s 399 end
Creates a request and parses the result as url_encoded. This is used internally for the RequestToken and AccessToken requests.
# File lib/oauth/consumer.rb 303 def token_request(http_method, path, token = nil, request_options = {}, *arguments, &block) 304 response = request(http_method, path, token, token_request_options(request_options), *arguments) 305 case response.code.to_i 306 307 when (200..299) 308 if block 309 block.call(response.body) 310 else 311 # symbolize keys 312 # TODO this could be considered unexpected behavior; symbols or not? 313 # TODO this also drops subsequent values from multi-valued keys 314 CGI.parse(response.body).each_with_object({}) do |(k, v), h| 315 h[k.strip.to_sym] = v.first 316 h[k.strip] = v.first 317 end 318 end 319 when (300..399) 320 current_uri = token_request_uri(path) 321 redirected_uri = token_request_redirect_uri(current_uri, response) 322 response.error! unless redirected_uri 323 324 redirect_count = request_options[:token_request_redirect_count].to_i + 1 325 response.error! if redirect_count > token_request_max_redirects(request_options) 326 response.error! if token_request_cross_origin?(current_uri, redirected_uri) && 327 !token_request_cross_origin_redirects?(request_options) 328 329 redirect_options = request_options.merge(token_request_redirect_count: redirect_count) 330 token_request(http_method, token_request_redirect_path(current_uri, redirected_uri), token, redirect_options, *arguments, &block) 331 when (400..499) 332 raise OAuth::Unauthorized, response 333 else 334 response.error! 335 end 336 end
# File lib/oauth/consumer.rb 374 def token_request_cross_origin?(current_uri, redirected_uri) 375 current_uri.scheme.to_s.downcase != redirected_uri.scheme.to_s.downcase || 376 current_uri.host.to_s.downcase != redirected_uri.host.to_s.downcase || 377 token_request_effective_port(current_uri) != token_request_effective_port(redirected_uri) 378 end
# File lib/oauth/consumer.rb 370 def token_request_cross_origin_redirects?(request_options) 371 request_options.fetch(:token_request_cross_origin_redirects, options[:token_request_cross_origin_redirects]) 372 end
# File lib/oauth/consumer.rb 380 def token_request_effective_port(uri) 381 return uri.port if uri.port 382 return 443 if uri.scheme == "https" 383 384 80 if uri.scheme == "http" 385 end
# File lib/oauth/consumer.rb 366 def token_request_max_redirects(request_options) 367 request_options[:token_request_max_redirects] || options[:token_request_max_redirects] 368 end
# File lib/oauth/consumer.rb 338 def token_request_options(request_options) 339 request_options.merge(token_request: true).tap do |options| 340 options.delete(:token_request_redirect_count) 341 options.delete(:token_request_max_redirects) 342 options.delete(:token_request_cross_origin_redirects) 343 end 344 end
# File lib/oauth/consumer.rb 360 def token_request_redirect_path(current_uri, redirected_uri) 361 return redirected_uri.to_s if token_request_cross_origin?(current_uri, redirected_uri) 362 363 redirected_uri.request_uri 364 end
# File lib/oauth/consumer.rb 353 def token_request_redirect_uri(current_uri, response) 354 location = response["location"] 355 return if location.nil? || location.to_s.empty? 356 357 current_uri.merge(location) 358 end
# File lib/oauth/consumer.rb 346 def token_request_uri(path) 347 uri = URI.parse(path) 348 return uri if uri.absolute? 349 350 URI.parse(site).merge(path) 351 end
Contains the root URI for this site
# File lib/oauth/consumer.rb 158 def uri(custom_uri = nil) 159 if custom_uri 160 @uri = custom_uri 161 @http = create_http # yike, oh well. less intrusive this way 162 else # if no custom passed, we use existing, which, if unset, is set to site uri 163 @uri ||= URI.parse(site) 164 end 165 end
Protected Instance Methods
Instantiates the http object
# File lib/oauth/consumer.rb 467 def create_http(url = nil) 468 url = request_endpoint unless request_endpoint.nil? 469 470 our_uri = if url.nil? || url[0] =~ %r{^/} 471 URI.parse(site) 472 else 473 your_uri = URI.parse(url) 474 if your_uri.host.nil? 475 # If the _url is a path, missing the leading slash, then it won't have a host, 476 # and our_uri *must* have a host, so we parse site instead. 477 URI.parse(site) 478 else 479 your_uri 480 end 481 end 482 483 if proxy.nil? 484 http_object = Net::HTTP.new(our_uri.host, our_uri.port) 485 else 486 proxy_uri = proxy.is_a?(URI) ? proxy : URI.parse(proxy) 487 http_object = Net::HTTP.new( 488 our_uri.host, 489 our_uri.port, 490 proxy_uri.host, 491 proxy_uri.port, 492 proxy_uri.user, 493 proxy_uri.password 494 ) 495 end 496 497 http_object.use_ssl = (our_uri.scheme == "https") 498 499 if @options[:no_verify] 500 http_object.verify_mode = OpenSSL::SSL::VERIFY_NONE 501 else 502 ca_file = @options[:ca_file] || CA_FILE 503 http_object.ca_file = ca_file if ca_file 504 http_object.verify_mode = OpenSSL::SSL::VERIFY_PEER 505 http_object.verify_depth = 5 506 end 507 508 http_object.read_timeout = http_object.open_timeout = @options[:timeout] || 60 509 http_object.open_timeout = @options[:open_timeout] if @options[:open_timeout] 510 http_object.ssl_version = @options[:ssl_version] if @options[:ssl_version] 511 http_object.cert = @options[:ssl_client_cert] if @options[:ssl_client_cert] 512 http_object.key = @options[:ssl_client_key] if @options[:ssl_client_key] 513 http_object.set_debug_output(debug_output) if debug_output 514 515 http_object 516 end
create the http request object for a given http_method and path
# File lib/oauth/consumer.rb 519 def create_http_request(http_method, path, *arguments) 520 http_method = http_method.to_sym 521 522 data = arguments.shift if %i[post put patch].include?(http_method) 523 524 # if the base site contains a path, add it now 525 # only add if the site host matches the current http object's host 526 # (in case we've specified a full url for token requests) 527 uri = URI.parse(site) 528 path = uri.path + path if uri.path && uri.path != "/" && uri.host == http.address 529 530 headers = arguments.first.is_a?(Hash) ? arguments.shift : {} 531 532 case http_method 533 when :post 534 request = Net::HTTP::Post.new(path, headers) 535 request["Content-Length"] = "0" # Default to 0 536 when :put 537 request = Net::HTTP::Put.new(path, headers) 538 request["Content-Length"] = "0" # Default to 0 539 when :patch 540 request = Net::HTTP::Patch.new(path, headers) 541 request["Content-Length"] = "0" # Default to 0 542 when :get 543 request = Net::HTTP::Get.new(path, headers) 544 when :delete 545 request = Net::HTTP::Delete.new(path, headers) 546 when :head 547 request = Net::HTTP::Head.new(path, headers) 548 else 549 raise ArgumentError, "Don't know how to handle http_method: :#{http_method}" 550 end 551 552 if data.is_a?(Hash) 553 request.body = OAuth::Helper.normalize(data) 554 request.content_type = "application/x-www-form-urlencoded" 555 elsif data 556 if data.respond_to?(:read) 557 request.body_stream = data 558 if data.respond_to?(:length) 559 request["Content-Length"] = data.length.to_s 560 elsif data.respond_to?(:stat) && data.stat.respond_to?(:size) 561 request["Content-Length"] = data.stat.size.to_s 562 else 563 raise ArgumentError, "Don't know how to send a body_stream that doesn't respond to .length or .stat.size" 564 end 565 else 566 request.body = data.to_s 567 request["Content-Length"] = request.body.length.to_s 568 end 569 end 570 571 request 572 end
# File lib/oauth/consumer.rb 574 def marshal_dump(*_args) 575 {key: @key, secret: @secret, options: @options} 576 end
# File lib/oauth/consumer.rb 578 def marshal_load(data) 579 initialize(data[:key], data[:secret], data[:options]) 580 end