├── .gemtest ├── Gemfile ├── .gitignore ├── lib ├── oauth │ ├── version.rb │ ├── client.rb │ ├── errors │ │ ├── error.rb │ │ ├── unauthorized.rb │ │ └── problem.rb │ ├── errors.rb │ ├── cli │ │ ├── version_command.rb │ │ ├── help_command.rb │ │ ├── query_command.rb │ │ ├── authorize_command.rb │ │ ├── sign_command.rb │ │ └── base_command.rb │ ├── tokens │ │ ├── server_token.rb │ │ ├── token.rb │ │ ├── consumer_token.rb │ │ ├── request_token.rb │ │ └── access_token.rb │ ├── request_proxy │ │ ├── action_dispatch_request.rb │ │ ├── jabber_request.rb │ │ ├── rack_request.rb │ │ ├── mock_request.rb │ │ ├── rest_client_request.rb │ │ ├── curb_request.rb │ │ ├── typhoeus_request.rb │ │ ├── em_http_request.rb │ │ ├── net_http.rb │ │ ├── action_controller_request.rb │ │ └── base.rb │ ├── token.rb │ ├── signature │ │ ├── hmac │ │ │ └── sha1.rb │ │ ├── plaintext.rb │ │ ├── rsa │ │ │ └── sha1.rb │ │ └── base.rb │ ├── oauth.rb │ ├── request_proxy.rb │ ├── oauth_test_helper.rb │ ├── cli.rb │ ├── signature.rb │ ├── server.rb │ ├── client │ │ ├── action_controller_request.rb │ │ ├── helper.rb │ │ ├── em_http.rb │ │ └── net_http.rb │ └── helper.rb └── oauth.rb ├── .codeclimate.yml ├── bin └── oauth ├── .travis.yml ├── Rakefile ├── test ├── units │ ├── test_token.rb │ ├── test_signature.rb │ ├── test_access_token.rb │ ├── test_signature_base.rb │ ├── test_hmac_sha1.rb │ ├── test_server.rb │ ├── test_signature_hmac_sha1.rb │ ├── test_signature_plain_text.rb │ ├── test_rack_request_proxy.rb │ ├── test_action_dispatch_request_proxy.rb │ ├── test_request_token.rb │ ├── test_curb_request_proxy.rb │ ├── test_net_http_request_proxy.rb │ ├── test_em_http_client.rb │ ├── test_oauth_helper.rb │ ├── test_rest_client_request_proxy.rb │ ├── test_rsa_sha1.rb │ ├── test_typhoeus_request_proxy.rb │ ├── test_client_helper.rb │ ├── test_action_controller_request_proxy.rb │ ├── test_em_http_request_proxy.rb │ ├── test_cli.rb │ └── test_consumer.rb ├── test_helper.rb ├── cases │ ├── oauth_case.rb │ └── spec │ │ └── 1_0-final │ │ ├── test_parameter_encodings.rb │ │ ├── test_construct_request_url.rb │ │ ├── test_signature_base_strings.rb │ │ └── test_normalize_request_parameters.rb ├── keys │ ├── rsa.cert │ └── rsa.pem ├── support │ └── minitest_helpers.rb └── integration │ └── consumer_test.rb ├── LICENSE ├── TODO ├── examples ├── twitter.rb └── yql.rb ├── oauth.gemspec ├── README.rdoc └── HISTORY /.gemtest: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | Gemfile.lock 3 | coverage 4 | *.gem 5 | -------------------------------------------------------------------------------- /lib/oauth/version.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | VERSION = "0.5.5" 3 | end 4 | -------------------------------------------------------------------------------- /lib/oauth/client.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | module Client 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/oauth/errors/error.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | class Error < StandardError 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/oauth/errors.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/errors/error' 2 | require 'oauth/errors/unauthorized' 3 | require 'oauth/errors/problem' 4 | -------------------------------------------------------------------------------- /lib/oauth/cli/version_command.rb: -------------------------------------------------------------------------------- 1 | class OAuth::CLI 2 | class VersionCommand < BaseCommand 3 | def run 4 | puts "OAuth Gem #{OAuth::VERSION}" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/oauth/tokens/server_token.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | # Used on the server for generating tokens 3 | class ServerToken < Token 4 | 5 | def initialize 6 | super(generate_key(16), generate_key) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/action_dispatch_request.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/request_proxy/rack_request' 2 | 3 | module OAuth::RequestProxy 4 | class ActionDispatchRequest < OAuth::RequestProxy::RackRequest 5 | proxies ActionDispatch::Request 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/oauth/token.rb: -------------------------------------------------------------------------------- 1 | # this exists for backwards-compatibility 2 | 3 | require 'oauth/tokens/token' 4 | require 'oauth/tokens/server_token' 5 | require 'oauth/tokens/consumer_token' 6 | require 'oauth/tokens/request_token' 7 | require 'oauth/tokens/access_token' 8 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | config: 6 | languages: 7 | - ruby 8 | fixme: 9 | enabled: true 10 | rubocop: 11 | enabled: true 12 | ratings: 13 | paths: 14 | - "**.rb" 15 | exclude_paths: 16 | - test/ 17 | -------------------------------------------------------------------------------- /lib/oauth/errors/unauthorized.rb: -------------------------------------------------------------------------------- 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 13 | -------------------------------------------------------------------------------- /bin/oauth: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../lib/oauth" 4 | require 'oauth/cli' 5 | 6 | Signal.trap("INT") { puts; exit(1) } # don't dump a backtrace on a ^C 7 | 8 | ARGV << 'help' if ARGV.empty? 9 | command = ARGV.shift 10 | 11 | OAuth::CLI.new(STDOUT, STDIN, STDERR, command, ARGV).run 12 | -------------------------------------------------------------------------------- /lib/oauth.rb: -------------------------------------------------------------------------------- 1 | root = File.dirname(__FILE__) 2 | $LOAD_PATH << root unless $LOAD_PATH.include?(root) 3 | 4 | require 'oauth/version' 5 | 6 | require 'oauth/oauth' 7 | 8 | require 'oauth/client/helper' 9 | require 'oauth/signature/hmac/sha1' 10 | require 'oauth/signature/rsa/sha1' 11 | require 'oauth/request_proxy/mock_request' 12 | -------------------------------------------------------------------------------- /lib/oauth/errors/problem.rb: -------------------------------------------------------------------------------- 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 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - "ruby-head" 5 | - "2.7" 6 | - "2.4.0" 7 | - "2.3" 8 | - "2.2" 9 | matrix: 10 | allow_failures: 11 | - rvm: "ruby-head" 12 | addons: 13 | code_climate: 14 | repo_token: 8f697ca756250f0c2c54170ae27e8a9c459d18a0236903b11291c88291b3aac9 15 | 16 | after_success: 17 | - bundle exec codeclimate-test-reporter 18 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | %w[rubygems rake rake/clean rake/testtask fileutils bundler].each { |f| require f } 2 | 3 | Bundler::GemHelper.install_tasks 4 | 5 | Rake::TestTask.new do |t| 6 | t.libs << "test" 7 | t.test_files = FileList['test/**/*test*.rb'] 8 | t.verbose = true 9 | end 10 | 11 | Dir['tasks/**/*.rake'].each { |t| load t } 12 | 13 | task :default => :test 14 | -------------------------------------------------------------------------------- /test/units/test_token.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'oauth/token' 4 | 5 | class TestToken < Minitest::Test 6 | 7 | def setup 8 | end 9 | 10 | def test_token_constructor_produces_valid_token 11 | token = OAuth::Token.new('xyz', '123') 12 | assert_equal 'xyz', token.token 13 | assert_equal '123', token.secret 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/oauth/tokens/token.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | # Superclass for the various tokens used by OAuth 3 | class Token 4 | include OAuth::Helper 5 | 6 | attr_accessor :token, :secret 7 | 8 | def initialize(token, secret) 9 | @token = token 10 | @secret = secret 11 | end 12 | 13 | def to_query 14 | "oauth_token=#{escape(token)}&oauth_token_secret=#{escape(secret)}" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/oauth/signature/hmac/sha1.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/signature/base' 2 | 3 | module OAuth::Signature::HMAC 4 | class SHA1 < OAuth::Signature::Base 5 | implements 'hmac-sha1' 6 | 7 | def body_hash 8 | Base64.encode64(OpenSSL::Digest::SHA1.digest(request.body || '')).chomp.gsub(/\n/,'') 9 | end 10 | 11 | private 12 | 13 | def digest 14 | OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), secret, signature_base_string) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/oauth/signature/plaintext.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/signature/base' 2 | 3 | module OAuth::Signature 4 | class PLAINTEXT < Base 5 | implements 'plaintext' 6 | 7 | def signature 8 | signature_base_string 9 | end 10 | 11 | def ==(cmp_signature) 12 | signature.to_s == cmp_signature.to_s 13 | end 14 | 15 | def signature_base_string 16 | secret 17 | end 18 | 19 | def body_hash 20 | nil 21 | end 22 | 23 | private 24 | 25 | def secret 26 | super 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # ensure test env 2 | 3 | ENV['RACK_ENV'] = 'test' 4 | 5 | # simplecov, Travis will call codeclimate 6 | 7 | require 'simplecov' 8 | SimpleCov.start 9 | 10 | # require third-party code 11 | 12 | require 'byebug' 13 | require 'stringio' 14 | require 'minitest/autorun' 15 | require 'mocha/mini_test' 16 | require 'rack/test' 17 | require 'webmock/minitest' 18 | 19 | # require our lib 20 | 21 | $LOAD_PATH << File.dirname(__FILE__) + '/../lib/' 22 | require 'oauth' 23 | 24 | # require our support code 25 | 26 | require 'support/minitest_helpers' 27 | -------------------------------------------------------------------------------- /lib/oauth/oauth.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | # request tokens are passed between the consumer and the provider out of 3 | # band (i.e. callbacks cannot be used), per section 6.1.1 4 | OUT_OF_BAND = "oob" 5 | 6 | # required parameters, per sections 6.1.1, 6.3.1, and 7 7 | PARAMETERS = %w(oauth_callback oauth_consumer_key oauth_token 8 | oauth_signature_method oauth_timestamp oauth_nonce oauth_verifier 9 | oauth_version oauth_signature oauth_body_hash) 10 | 11 | # reserved character regexp, per section 5.1 12 | RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~]/ 13 | end 14 | -------------------------------------------------------------------------------- /test/units/test_signature.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | require File.expand_path('../../test_helper', __FILE__) 4 | 5 | class TestOauth < Minitest::Test 6 | 7 | def test_parameter_escaping_kcode_invariant 8 | %w(n N e E s S u U).each do |kcode| 9 | assert_equal '%E3%81%82', OAuth::Helper.escape('あ'), 10 | "Failed to correctly escape Japanese under $KCODE = #{kcode}" 11 | assert_equal '%C3%A9', OAuth::Helper.escape('é'), 12 | "Failed to correctly escape e+acute under $KCODE = #{kcode}" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/cases/oauth_case.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'oauth/signature' 3 | require 'oauth/request_proxy/mock_request' 4 | 5 | 6 | class OAuthCase < Minitest::Test 7 | # avoid whining about a lack of tests 8 | def run(*args) 9 | @method_name ||= nil 10 | return if @method_name.to_s == "default_test" 11 | super 12 | end 13 | 14 | protected 15 | 16 | # Creates a fake request 17 | def request(params={},method='GET',uri="http://photos.example.net/photos") 18 | OAuth::RequestProxy.proxy({'parameters'=>params,'method'=>method,'uri'=>uri}) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/oauth/cli/help_command.rb: -------------------------------------------------------------------------------- 1 | class OAuth::CLI 2 | class HelpCommand < BaseCommand 3 | def run 4 | puts <<-EOT 5 | Usage: oauth COMMAND [ARGS] 6 | 7 | Available oauth commands are: 8 | a, authorize Obtain an access token and secret for a user 9 | q, query Query a protected resource 10 | s, sign Generate an OAuth signature 11 | 12 | In addition to those, there are: 13 | v, version Displays the current version of the library (or --version, -v) 14 | h, help Displays this help (or --help, -h) 15 | 16 | Tip: All commands can be run without args for specific help. 17 | 18 | 19 | EOT 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/keys/rsa.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBpjCCAQ+gAwIBAgIBATANBgkqhkiG9w0BAQUFADAZMRcwFQYDVQQDDA5UZXN0 3 | IFByaW5jaXBhbDAeFw03MDAxMDEwODAwMDBaFw0zODEyMzEwODAwMDBaMBkxFzAV 4 | BgNVBAMMDlRlc3QgUHJpbmNpcGFsMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 5 | gQC0YjCwIfYoprq/FQO6lb3asXrxLlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlY 6 | zypSRjVxwxrsuRcP3e641SdASwfrmzyvIgP08N4S0IFzEURkV1wp/IpH7kH41Etb 7 | mUmrXSwfNZsnQRE5SYSOhh+LcK2wyQkdgcMv11l4KoBkcwIDAQABMA0GCSqGSIb3 8 | DQEBBQUAA4GBAGZLPEuJ5SiJ2ryq+CmEGOXfvlTtEL2nuGtr9PewxkgnOjZpUy+d 9 | 4TvuXJbNQc8f4AMWL/tO9w0Fk80rWKp9ea8/df4qMq5qlFWlx6yOLQxumNOmECKb 10 | WpkUQDIDJEoFUzKMVuJf4KO/FJ345+BNLGgbJ6WujreoM1X/gYfdnJ/J 11 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /lib/oauth/request_proxy.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | module RequestProxy 3 | def self.available_proxies #:nodoc: 4 | @available_proxies ||= {} 5 | end 6 | 7 | def self.proxy(request, options = {}) 8 | return request if request.kind_of?(OAuth::RequestProxy::Base) 9 | 10 | klass = available_proxies[request.class] 11 | 12 | # Search for possible superclass matches. 13 | if klass.nil? 14 | request_parent = available_proxies.keys.find { |rc| request.kind_of?(rc) } 15 | klass = available_proxies[request_parent] 16 | end 17 | 18 | raise UnknownRequestType, request.class.to_s unless klass 19 | klass.new(request, options) 20 | end 21 | 22 | class UnknownRequestType < Exception; end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/units/test_access_token.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class TestAccessToken < Minitest::Test 4 | def setup 5 | @fake_response = { 6 | :user_id => 5734758743895, 7 | :oauth_token => "key", 8 | :oauth_token_secret => "secret" 9 | } 10 | # setup a fake req. token. mocking Consumer would be more appropriate... 11 | @access_token = OAuth::AccessToken.from_hash( 12 | OAuth::Consumer.new("key", "secret", {}), 13 | @fake_response 14 | ) 15 | end 16 | 17 | def test_provides_response_parameters 18 | assert @access_token 19 | assert_respond_to @access_token, :params 20 | end 21 | 22 | def test_access_token_makes_non_oauth_response_params_available 23 | assert @access_token.params[:user_id] 24 | assert_equal 5734758743895, @access_token.params[:user_id] 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/oauth/oauth_test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'action_controller' 2 | require 'action_controller/test_process' 3 | 4 | module OAuth 5 | module OAuthTestHelper 6 | def mock_incoming_request_with_query(request) 7 | incoming = ActionController::TestRequest.new(request.to_hash) 8 | incoming.request_uri = request.path 9 | incoming.host = request.uri.host 10 | incoming.env["SERVER_PORT"] = request.uri.port 11 | incoming.env['REQUEST_METHOD'] = request.http_method 12 | incoming 13 | end 14 | 15 | def mock_incoming_request_with_authorize_header(request) 16 | incoming = ActionController::TestRequest.new 17 | incoming.request_uri = request.path 18 | incoming.host = request.uri.host 19 | incoming.env["HTTP_AUTHORIZATION"] = request.to_auth_string 20 | incoming.env["SERVER_PORT"] = request.uri.port 21 | incoming.env['REQUEST_METHOD'] = request.http_method 22 | incoming 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/units/test_signature_base.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'oauth/signature/base' 4 | require 'net/http' 5 | 6 | class SignatureBaseTest < Minitest::Test 7 | 8 | def test_that_initialize_requires_one_request_argument 9 | assert_raises ArgumentError do 10 | OAuth::Signature::Base.new() 11 | end 12 | end 13 | 14 | def test_that_initialize_requires_a_valid_request_argument 15 | request = nil 16 | assert_raises TypeError do 17 | OAuth::Signature::Base.new(request) { |token| 18 | # just a stub 19 | } 20 | end 21 | end 22 | 23 | def test_that_initialize_succeeds_when_the_request_proxy_is_valid 24 | # this isn't quite valid, but it will do. 25 | raw_request = Net::HTTP::Get.new('/test') 26 | request = OAuth::RequestProxy.proxy(raw_request) 27 | 28 | OAuth::Signature::Base.new(request) { |token| 29 | # just a stub 30 | } 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /test/keys/rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V 3 | A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d 4 | 7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ 5 | hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H 6 | X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm 7 | uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw 8 | rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z 9 | zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn 10 | qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG 11 | WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno 12 | cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ 13 | 3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 14 | AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 15 | Lw03eHTNQghS0A== 16 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /lib/oauth/cli/query_command.rb: -------------------------------------------------------------------------------- 1 | class OAuth::CLI 2 | class QueryCommand < BaseCommand 3 | extend OAuth::Helper 4 | 5 | def required_options 6 | [:oauth_consumer_key, :oauth_consumer_secret, :oauth_token, :oauth_token_secret] 7 | end 8 | 9 | def _run 10 | consumer = OAuth::Consumer.new(options[:oauth_consumer_key], options[:oauth_consumer_secret], scheme: options[:scheme]) 11 | 12 | access_token = OAuth::AccessToken.new(consumer, options[:oauth_token], options[:oauth_token_secret]) 13 | 14 | # append params to the URL 15 | uri = URI.parse(options[:uri]) 16 | params = parameters.map { |k,v| Array(v).map { |v2| "#{OAuth::Helper.escape(k)}=#{OAuth::Helper.escape(v2)}" } * "&" } 17 | uri.query = [uri.query, *params].reject { |x| x.nil? } * "&" 18 | puts uri.to_s 19 | 20 | response = access_token.request(options[:method].to_s.downcase.to_sym, uri.to_s) 21 | puts "#{response.code} #{response.message}" 22 | puts response.body 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/units/test_hmac_sha1.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class TestSignatureHmacSha1 < Minitest::Test 4 | def test_that_hmac_sha1_implements_hmac_sha1 5 | assert OAuth::Signature.available_methods.include?('hmac-sha1') 6 | end 7 | 8 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature 9 | request = Net::HTTP::Get.new('/photos?file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_token=nnch734d00sl2jdk&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1') 10 | 11 | consumer = OAuth::Consumer.new('dpf43f3p2l4k3l03', 'kd94hf93k423kf44') 12 | token = OAuth::Token.new('nnch734d00sl2jdk', 'pfkkdhi9sl3r4s00') 13 | 14 | signature = OAuth::Signature.sign(request, { :consumer => consumer, 15 | :token => token, 16 | :uri => 'http://photos.example.net/photos' } ) 17 | 18 | assert_equal 'tR3+Ty81lMeYAr/Fid0kMTYa/WM=', signature 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/jabber_request.rb: -------------------------------------------------------------------------------- 1 | require 'xmpp4r' 2 | require 'oauth/request_proxy/base' 3 | 4 | module OAuth 5 | module RequestProxy 6 | class JabberRequest < OAuth::RequestProxy::Base 7 | proxies Jabber::Iq 8 | proxies Jabber::Presence 9 | proxies Jabber::Message 10 | 11 | def parameters 12 | return @params if @params 13 | 14 | @params = {} 15 | 16 | oauth = @request.get_elements('//oauth').first 17 | return @params unless oauth 18 | 19 | %w( oauth_token oauth_consumer_key oauth_signature_method oauth_signature 20 | oauth_timestamp oauth_nonce oauth_version ).each do |param| 21 | next unless element = oauth.first_element(param) 22 | @params[param] = element.text 23 | end 24 | 25 | @params 26 | end 27 | 28 | def method 29 | @request.name 30 | end 31 | 32 | def uri 33 | [@request.from.strip.to_s, @request.to.strip.to_s].join("&") 34 | end 35 | 36 | def normalized_uri 37 | uri 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/rack_request.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/request_proxy/base' 2 | require 'uri' 3 | require 'rack' 4 | 5 | module OAuth::RequestProxy 6 | class RackRequest < OAuth::RequestProxy::Base 7 | proxies Rack::Request 8 | 9 | def method 10 | request.env["rack.methodoverride.original_method"] || request.request_method 11 | end 12 | 13 | def uri 14 | request.url 15 | end 16 | 17 | def parameters 18 | if options[:clobber_request] 19 | options[:parameters] || {} 20 | else 21 | params = request_params.merge(query_params).merge(header_params) 22 | params.merge(options[:parameters] || {}) 23 | end 24 | end 25 | 26 | def signature 27 | parameters['oauth_signature'] 28 | end 29 | 30 | protected 31 | 32 | def query_params 33 | request.GET 34 | end 35 | 36 | def request_params 37 | if request.content_type and request.content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded") 38 | request.POST 39 | else 40 | {} 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/oauth/tokens/consumer_token.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | # Superclass for tokens used by OAuth Clients 3 | class ConsumerToken < Token 4 | attr_accessor :consumer, :params 5 | attr_reader :response 6 | 7 | def self.from_hash(consumer, hash) 8 | token = self.new(consumer, hash[:oauth_token], hash[:oauth_token_secret]) 9 | token.params = hash 10 | token 11 | end 12 | 13 | def initialize(consumer, token="", secret="") 14 | super(token, secret) 15 | @consumer = consumer 16 | @params = {} 17 | end 18 | 19 | # Make a signed request using given http_method to the path 20 | # 21 | # @token.request(:get, '/people') 22 | # @token.request(:post, '/people', @person.to_xml, { 'Content-Type' => 'application/xml' }) 23 | # 24 | def request(http_method, path, *arguments) 25 | @response = consumer.request(http_method, path, self, {}, *arguments) 26 | end 27 | 28 | # Sign a request generated elsewhere using Net:HTTP::Post.new or friends 29 | def sign!(request, options = {}) 30 | consumer.sign!(request, self, options) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007 Blaine Cook, Larry Halff, Pelle Braendgaard 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Common use-cases should be streamlined: 2 | 3 | * I have a URL that I want to sign (given consumer key/secret, optional 4 | token/secret, optional nonce/timestamp). 5 | * I have a URL that I want to sign AND I want to see what the components 6 | (e.g. signature base string, etc.) are while it's being signed (i.e. verbose 7 | signing). 8 | * I have a URL that I want to sign and I only want the signature. 9 | * I have a URL that I want to sign and I want something suitable to put in 10 | {the header, the querystring, XMPP}. 11 | * I want to make a query to an OAuth-enabled web service (with sensible 12 | errors, if available). 13 | * I want to host an OAuth-enabled web service. 14 | * I want to test my OAuth-enabled web service (i.e. test helpers) 15 | 16 | Example applications for: 17 | * Ning 18 | * Fire Eagle 19 | * Google (blogger, contacts) 20 | * Twitter 21 | * YOS / YQL 22 | * Netflix 23 | 24 | In addition to providing best practices of use, these can also be part of 25 | the pre-release checks to make sure that there have been no regressions. 26 | 27 | Random TODOs: 28 | * finish CLI 29 | * sensible Exception hierarchy 30 | * Tokens as Modules 31 | * don't tie to Net::HTTP 32 | * Take a look at Curb HTTP Verbs -------------------------------------------------------------------------------- /examples/twitter.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -r rubygems 2 | # 3 | # ./twitter.rb --consumer-key --consumer-secret 4 | 5 | require 'oauth' 6 | require 'optparse' 7 | require 'json' 8 | require 'pp' 9 | 10 | options = {} 11 | 12 | option_parser = OptionParser.new do |opts| 13 | opts.banner = "Usage: #{$0} [options] " 14 | 15 | opts.on("--consumer-key KEY", "Specifies the consumer key to use.") do |v| 16 | options[:consumer_key] = v 17 | end 18 | 19 | opts.on("--consumer-secret SECRET", "Specifies the consumer secret to use.") do |v| 20 | options[:consumer_secret] = v 21 | end 22 | end 23 | 24 | option_parser.parse! 25 | query = ARGV.pop 26 | query = STDIN.read if query == "-" 27 | 28 | if options[:consumer_key].nil? || options[:consumer_secret].nil? || query.nil? 29 | puts option_parser.help 30 | exit 1 31 | end 32 | 33 | consumer = OAuth::Consumer.new \ 34 | options[:consumer_key], 35 | options[:consumer_secret], 36 | :site => "https://api.twitter.com" 37 | 38 | access_token = OAuth::AccessToken.new(consumer) 39 | 40 | response = access_token.request(:get, "/1.1/statuses/show/#{OAuth::Helper.escape(query)}.json") 41 | rsp = JSON.parse(response.body) 42 | pp rsp 43 | -------------------------------------------------------------------------------- /test/support/minitest_helpers.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | module MinitestHelpers 3 | def assert_matching_headers(expected, actual) 4 | # transform into sorted arrays 5 | auth_intro, auth_params = actual.split(' ', 2) 6 | assert_equal auth_intro, 'OAuth' 7 | expected = expected.split(/(,|\s)/).reject {|v| v == '' || v =~ /^[\,\s]+/}.sort 8 | auth_params = auth_params.split(/(,|\s)/).reject {|v| v == '' || v =~ /^[\,\s]+/}.sort 9 | assert_equal expected, auth_params 10 | end 11 | 12 | def stub_test_ie 13 | stub_request(:any, "http://term.ie/oauth/example/request_token.php").to_return(:body => "oauth_token=requestkey&oauth_token_secret=requestsecret") 14 | stub_request(:post, "http://term.ie/oauth/example/access_token.php").to_return(:body => "oauth_token=accesskey&oauth_token_secret=accesssecret") 15 | stub_request(:get, %r{http://term\.ie/oauth/example/echo_api\.php\?.+}).to_return(lambda {|request| {:body => request.uri.query}}) 16 | stub_request(:post, "http://term.ie/oauth/example/echo_api.php").to_return(lambda {|request| {:body => request.body}}) 17 | end 18 | end 19 | end 20 | 21 | # TODO: When dropping support to Ruby 2.0, stop using send 22 | Minitest::Test.send(:include, OAuth::MinitestHelpers) 23 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/mock_request.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/request_proxy/base' 2 | 3 | module OAuth 4 | module RequestProxy 5 | # RequestProxy for Hashes to facilitate simpler signature creation. 6 | # Usage: 7 | # request = OAuth::RequestProxy.proxy \ 8 | # "method" => "iq", 9 | # "uri" => [from, to] * "&", 10 | # "parameters" => { 11 | # "oauth_consumer_key" => oauth_consumer_key, 12 | # "oauth_token" => oauth_token, 13 | # "oauth_signature_method" => "HMAC-SHA1" 14 | # } 15 | # 16 | # signature = OAuth::Signature.sign \ 17 | # request, 18 | # :consumer_secret => oauth_consumer_secret, 19 | # :token_secret => oauth_token_secret, 20 | class MockRequest < OAuth::RequestProxy::Base 21 | proxies Hash 22 | 23 | def parameters 24 | @request["parameters"] 25 | end 26 | 27 | def method 28 | @request["method"] 29 | end 30 | 31 | def normalized_uri 32 | super 33 | rescue 34 | # if this is a non-standard URI, it may not parse properly 35 | # in that case, assume that it's already been normalized 36 | uri 37 | end 38 | 39 | def uri 40 | @request["uri"] 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /examples/yql.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -r rubygems 2 | 3 | # Sample queries: 4 | # ./yql.rb --consumer-key --consumer-secret "show tables" 5 | # ./yql.rb --consumer-key --consumer-secret "select * from flickr.photos.search where text='Cat' limit 10" 6 | 7 | require 'oauth' 8 | require 'optparse' 9 | require 'json' 10 | require 'pp' 11 | 12 | options = {} 13 | 14 | option_parser = OptionParser.new do |opts| 15 | opts.banner = "Usage: #{$0} [options] " 16 | 17 | opts.on("--consumer-key KEY", "Specifies the consumer key to use.") do |v| 18 | options[:consumer_key] = v 19 | end 20 | 21 | opts.on("--consumer-secret SECRET", "Specifies the consumer secret to use.") do |v| 22 | options[:consumer_secret] = v 23 | end 24 | end 25 | 26 | option_parser.parse! 27 | query = ARGV.pop 28 | query = STDIN.read if query == "-" 29 | 30 | if options[:consumer_key].nil? || options[:consumer_secret].nil? || query.nil? 31 | puts option_parser.help 32 | exit 1 33 | end 34 | 35 | consumer = OAuth::Consumer.new \ 36 | options[:consumer_key], 37 | options[:consumer_secret], 38 | :site => "http://query.yahooapis.com" 39 | 40 | access_token = OAuth::AccessToken.new(consumer) 41 | 42 | response = access_token.request(:get, "/v1/yql?q=#{OAuth::Helper.escape(query)}&format=json") 43 | rsp = JSON.parse(response.body) 44 | pp rsp 45 | -------------------------------------------------------------------------------- /lib/oauth/signature/rsa/sha1.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/signature/base' 2 | 3 | module OAuth::Signature::RSA 4 | class SHA1 < OAuth::Signature::Base 5 | implements 'rsa-sha1' 6 | 7 | def ==(cmp_signature) 8 | public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(cmp_signature.is_a?(Array) ? cmp_signature.first : cmp_signature), signature_base_string) 9 | end 10 | 11 | def public_key 12 | if consumer_secret.is_a?(String) 13 | decode_public_key 14 | elsif consumer_secret.is_a?(OpenSSL::X509::Certificate) 15 | consumer_secret.public_key 16 | else 17 | consumer_secret 18 | end 19 | end 20 | 21 | def body_hash 22 | Base64.encode64(OpenSSL::Digest::SHA1.digest(request.body || '')).chomp.gsub(/\n/,'') 23 | end 24 | 25 | private 26 | 27 | def decode_public_key 28 | case consumer_secret 29 | when /-----BEGIN CERTIFICATE-----/ 30 | OpenSSL::X509::Certificate.new( consumer_secret).public_key 31 | else 32 | OpenSSL::PKey::RSA.new( consumer_secret) 33 | end 34 | end 35 | 36 | def digest 37 | private_key = OpenSSL::PKey::RSA.new( 38 | if options[:private_key_file] 39 | IO.read(options[:private_key_file]) 40 | elsif options[:private_key] 41 | options[:private_key] 42 | else 43 | consumer_secret 44 | end 45 | ) 46 | 47 | private_key.sign(OpenSSL::Digest::SHA1.new, signature_base_string) 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/oauth/cli.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | require 'oauth/cli/base_command' 3 | require 'oauth/cli/help_command' 4 | require 'oauth/cli/query_command' 5 | require 'oauth/cli/authorize_command' 6 | require 'oauth/cli/sign_command' 7 | require 'oauth/cli/version_command' 8 | require 'active_support/core_ext/string/inflections' 9 | 10 | module OAuth 11 | class CLI 12 | def self.puts_red(string) 13 | puts "\033[0;91m#{string}\033[0m" 14 | end 15 | 16 | ALIASES = { 17 | 'h' => 'help', 18 | 'v' => 'version', 19 | 'q' => 'query', 20 | 'a' => 'authorize', 21 | 's' => 'sign', 22 | } 23 | 24 | def initialize(stdout, stdin, stderr, command, arguments) 25 | klass = get_command_class(parse_command(command)) 26 | @command = klass.new(stdout, stdin, stderr, arguments) 27 | @help_command = HelpCommand.new(stdout, stdin, stderr, []) 28 | end 29 | 30 | def run 31 | @command.run 32 | end 33 | 34 | private 35 | 36 | def get_command_class(command) 37 | Object.const_get("OAuth::CLI::#{command.camelize}Command") 38 | end 39 | 40 | def parse_command(command) 41 | case command = command.to_s.downcase 42 | when '--version', '-v' 43 | 'version' 44 | when '--help', '-h', nil, '' 45 | 'help' 46 | when *ALIASES.keys 47 | ALIASES[command] 48 | when *ALIASES.values 49 | command 50 | else 51 | OAuth::CLI.puts_red "Command '#{command}' not found" 52 | 'help' 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/units/test_server.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'oauth/server' 4 | 5 | class ServerTest < Minitest::Test 6 | def setup 7 | @server=OAuth::Server.new "http://test.com" 8 | end 9 | 10 | def test_default_paths 11 | assert_equal "/oauth/request_token",@server.request_token_path 12 | assert_equal "/oauth/authorize",@server.authorize_path 13 | assert_equal "/oauth/access_token",@server.access_token_path 14 | end 15 | 16 | def test_default_urls 17 | assert_equal "http://test.com/oauth/request_token",@server.request_token_url 18 | assert_equal "http://test.com/oauth/authorize",@server.authorize_url 19 | assert_equal "http://test.com/oauth/access_token",@server.access_token_url 20 | end 21 | 22 | def test_generate_consumer_credentials 23 | consumer=@server.generate_consumer_credentials 24 | assert consumer.key 25 | assert consumer.secret 26 | end 27 | 28 | def test_create_consumer 29 | @consumer=@server.create_consumer 30 | assert @consumer 31 | assert @consumer.key 32 | assert @consumer.secret 33 | assert_equal "http://test.com",@consumer.site 34 | assert_equal "/oauth/request_token",@consumer.request_token_path 35 | assert_equal "/oauth/authorize",@consumer.authorize_path 36 | assert_equal "/oauth/access_token",@consumer.access_token_path 37 | assert_equal "http://test.com/oauth/request_token",@consumer.request_token_url 38 | assert_equal "http://test.com/oauth/authorize",@consumer.authorize_url 39 | assert_equal "http://test.com/oauth/access_token",@consumer.access_token_url 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/rest_client_request.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/request_proxy/base' 2 | require 'rest-client' 3 | require 'uri' 4 | require 'cgi' 5 | 6 | module OAuth::RequestProxy::RestClient 7 | class Request < OAuth::RequestProxy::Base 8 | proxies RestClient::Request 9 | 10 | def method 11 | request.method.to_s.upcase 12 | end 13 | 14 | def uri 15 | request.url 16 | end 17 | 18 | def parameters 19 | if options[:clobber_request] 20 | options[:parameters] || {} 21 | else 22 | post_parameters.merge(query_params).merge(options[:parameters] || {}) 23 | end 24 | end 25 | 26 | protected 27 | 28 | def query_params 29 | query = URI.parse(request.url).query 30 | query ? CGI.parse(query) : {} 31 | end 32 | 33 | def request_params 34 | end 35 | 36 | def post_parameters 37 | # Post params are only used if posting form data 38 | if method == 'POST' || method == 'PUT' 39 | OAuth::Helper.stringify_keys(query_string_to_hash(request.payload.to_s) || {}) 40 | else 41 | {} 42 | end 43 | end 44 | 45 | private 46 | 47 | def query_string_to_hash(query) 48 | keyvals = query.split('&').inject({}) do |result, q| 49 | k,v = q.split('=') 50 | if !v.nil? 51 | result.merge({k => v}) 52 | elsif !result.key?(k) 53 | result.merge({k => true}) 54 | else 55 | result 56 | end 57 | end 58 | keyvals 59 | end 60 | 61 | end 62 | end -------------------------------------------------------------------------------- /lib/oauth/tokens/request_token.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | # The RequestToken is used for the initial Request. 3 | # This is normally created by the Consumer object. 4 | class RequestToken < ConsumerToken 5 | 6 | # Generate an authorization URL for user authorization 7 | def authorize_url(params = nil) 8 | return nil if self.token.nil? 9 | 10 | params = (params || {}).merge(:oauth_token => self.token) 11 | build_url(consumer.authorize_url, params) 12 | end 13 | 14 | def authenticate_url(params = nil) 15 | return nil if self.token.nil? 16 | 17 | params = (params || {}).merge(:oauth_token => self.token) 18 | build_url(consumer.authenticate_url, params) 19 | end 20 | 21 | def callback_confirmed? 22 | params[:oauth_callback_confirmed] == "true" 23 | end 24 | 25 | # exchange for AccessToken on server 26 | def get_access_token(options = {}, *arguments) 27 | response = consumer.token_request(consumer.http_method, (consumer.access_token_url? ? consumer.access_token_url : consumer.access_token_path), self, options, *arguments) 28 | OAuth::AccessToken.from_hash(consumer, response) 29 | end 30 | 31 | protected 32 | 33 | # construct an authorization or authentication url 34 | def build_url(base_url, params) 35 | uri = URI.parse(base_url.to_s) 36 | queries = {} 37 | queries = Hash[URI.decode_www_form(uri.query)] if uri.query 38 | # TODO doesn't handle array values correctly 39 | queries.merge!(params) if params 40 | uri.query = URI.encode_www_form(queries) if !queries.empty? 41 | uri.to_s 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/curb_request.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/request_proxy/base' 2 | require 'curb' 3 | require 'uri' 4 | require 'cgi' 5 | 6 | module OAuth::RequestProxy::Curl 7 | class Easy < OAuth::RequestProxy::Base 8 | # Proxy for signing Curl::Easy requests 9 | # Usage example: 10 | # oauth_params = {:consumer => oauth_consumer, :token => access_token} 11 | # req = Curl::Easy.new(uri) 12 | # oauth_helper = OAuth::Client::Helper.new(req, oauth_params.merge(:request_uri => uri)) 13 | # req.headers.merge!({"Authorization" => oauth_helper.header}) 14 | # req.http_get 15 | # response = req.body_str 16 | proxies ::Curl::Easy 17 | 18 | def method 19 | nil 20 | end 21 | 22 | def uri 23 | options[:uri].to_s 24 | end 25 | 26 | def parameters 27 | if options[:clobber_request] 28 | options[:parameters] 29 | else 30 | post_parameters.merge(query_parameters).merge(options[:parameters] || {}) 31 | end 32 | end 33 | 34 | private 35 | 36 | def query_parameters 37 | query = URI.parse(request.url).query 38 | return(query ? CGI.parse(query) : {}) 39 | end 40 | 41 | def post_parameters 42 | post_body = {} 43 | 44 | # Post params are only used if posting form data 45 | if (request.headers['Content-Type'] && request.headers['Content-Type'].to_s.downcase.start_with?("application/x-www-form-urlencoded")) 46 | 47 | request.post_body.split("&").each do |str| 48 | param = str.split("=") 49 | post_body[param[0]] = param[1] 50 | end 51 | end 52 | post_body 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/typhoeus_request.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/request_proxy/base' 2 | require 'typhoeus' 3 | require 'typhoeus/request' 4 | require 'uri' 5 | require 'cgi' 6 | 7 | module OAuth::RequestProxy::Typhoeus 8 | class Request < OAuth::RequestProxy::Base 9 | # Proxy for signing Typhoeus::Request requests 10 | # Usage example: 11 | # oauth_params = {:consumer => oauth_consumer, :token => access_token} 12 | # req = Typhoeus::Request.new(uri, options) 13 | # oauth_helper = OAuth::Client::Helper.new(req, oauth_params.merge(:request_uri => uri)) 14 | # req.options[:headers].merge!({"Authorization" => oauth_helper.header}) 15 | # hydra = Typhoeus::Hydra.new() 16 | # hydra.queue(req) 17 | # hydra.run 18 | # response = req.response 19 | proxies Typhoeus::Request 20 | 21 | def method 22 | request_method = request.options[:method].to_s.upcase 23 | request_method.empty? ? 'GET' : request_method 24 | end 25 | 26 | def uri 27 | options[:uri].to_s 28 | end 29 | 30 | def parameters 31 | if options[:clobber_request] 32 | options[:parameters] 33 | else 34 | post_parameters.merge(query_parameters).merge(options[:parameters] || {}) 35 | end 36 | end 37 | 38 | private 39 | 40 | def query_parameters 41 | query = URI.parse(request.url).query 42 | query ? CGI.parse(query) : {} 43 | end 44 | 45 | def post_parameters 46 | # Post params are only used if posting form data 47 | if method == 'POST' 48 | OAuth::Helper.stringify_keys(request.options[:params] || {}) 49 | else 50 | {} 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/oauth/signature.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | module Signature 3 | # Returns a list of available signature methods 4 | def self.available_methods 5 | @available_methods ||= {} 6 | end 7 | 8 | # Build a signature from a +request+. 9 | # 10 | # Raises UnknownSignatureMethod exception if the signature method is unknown. 11 | def self.build(request, options = {}, &block) 12 | request = OAuth::RequestProxy.proxy(request, options) 13 | klass = available_methods[ 14 | (request.signature_method || 15 | ((c = request.options[:consumer]) && c.options[:signature_method]) || 16 | "").downcase] 17 | raise UnknownSignatureMethod, request.signature_method unless klass 18 | klass.new(request, options, &block) 19 | end 20 | 21 | # Sign a +request+ 22 | def self.sign(request, options = {}, &block) 23 | self.build(request, options, &block).signature 24 | end 25 | 26 | # Verify the signature of +request+ 27 | def self.verify(request, options = {}, &block) 28 | self.build(request, options, &block).verify 29 | end 30 | 31 | # Create the signature base string for +request+. This string is the normalized parameter information. 32 | # 33 | # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1] 34 | def self.signature_base_string(request, options = {}, &block) 35 | self.build(request, options, &block).signature_base_string 36 | end 37 | 38 | # Create the body hash for a request 39 | def self.body_hash(request, options = {}, &block) 40 | self.build(request, options, &block).body_hash 41 | end 42 | 43 | class UnknownSignatureMethod < Exception; end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/oauth/server.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/helper' 2 | require 'oauth/consumer' 3 | 4 | module OAuth 5 | # This is mainly used to create consumer credentials and can pretty much be ignored if you want to create your own 6 | class Server 7 | include OAuth::Helper 8 | attr_accessor :base_url 9 | 10 | @@server_paths = { 11 | :request_token_path => "/oauth/request_token", 12 | :authorize_path => "/oauth/authorize", 13 | :access_token_path => "/oauth/access_token" 14 | } 15 | 16 | # Create a new server instance 17 | def initialize(base_url, paths = {}) 18 | @base_url = base_url 19 | @paths = @@server_paths.merge(paths) 20 | end 21 | 22 | def generate_credentials 23 | [generate_key(16), generate_key] 24 | end 25 | 26 | def generate_consumer_credentials(params = {}) 27 | Consumer.new(*generate_credentials) 28 | end 29 | 30 | # mainly for testing purposes 31 | def create_consumer 32 | creds = generate_credentials 33 | Consumer.new(creds[0], creds[1], 34 | { 35 | :site => base_url, 36 | :request_token_path => request_token_path, 37 | :authorize_path => authorize_path, 38 | :access_token_path => access_token_path 39 | }) 40 | end 41 | 42 | def request_token_path 43 | @paths[:request_token_path] 44 | end 45 | 46 | def request_token_url 47 | base_url + request_token_path 48 | end 49 | 50 | def authorize_path 51 | @paths[:authorize_path] 52 | end 53 | 54 | def authorize_url 55 | base_url + authorize_path 56 | end 57 | 58 | def access_token_path 59 | @paths[:access_token_path] 60 | end 61 | 62 | def access_token_url 63 | base_url + access_token_path 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /test/units/test_signature_hmac_sha1.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'oauth/signature/hmac/sha1' 4 | 5 | class SignatureHMACSHA1Test < Minitest::Test 6 | def test_that_verify_returns_true_when_the_request_signature_is_right 7 | request = OAuth::RequestProxy::MockRequest.new( 8 | 'method' => 'POST', 9 | 'uri' => 'https://photos.example.net/initialize', 10 | 'parameters' => { 11 | 'oauth_consumer_key' => 'dpf43f3p2l4k3l03', 12 | 'oauth_signature_method' => 'HMAC-SHA1', 13 | 'oauth_timestamp' => '137131200', 14 | 'oauth_nonce' => 'wIjqoS', 15 | 'oauth_callback' => 'http://printer.example.com/ready', 16 | 'oauth_version' => '1.0', 17 | 'oauth_signature' => 'xcHYBV3AbyoDz7L4dV10P3oLCjY=' 18 | } 19 | ) 20 | assert OAuth::Signature::HMAC::SHA1.new(request, :consumer_secret => 'kd94hf93k423kf44').verify 21 | end 22 | 23 | def test_that_verify_returns_false_when_the_request_signature_is_wrong 24 | # Test a bug in the OAuth::Signature::Base#== method: when the Base64.decode64 method is 25 | # used on the "self" and "other" signature (as in version 0.4.7), the result may be incorrectly "true". 26 | request = OAuth::RequestProxy::MockRequest.new( 27 | 'method' => 'POST', 28 | 'uri' => 'https://photos.example.net/initialize', 29 | 'parameters' => { 30 | 'oauth_consumer_key' => 'dpf43f3p2l4k3l03', 31 | 'oauth_signature_method' => 'HMAC-SHA1', 32 | 'oauth_timestamp' => '137131200', 33 | 'oauth_nonce' => 'wIjqoS', 34 | 'oauth_callback' => 'http://printer.example.com/ready', 35 | 'oauth_version' => '1.0', 36 | 'oauth_signature' => 'xcHYBV3AbyoDz7L4dV10P3oLCjZ=' 37 | } 38 | ) 39 | assert !OAuth::Signature::HMAC::SHA1.new(request, :consumer_secret => 'kd94hf93k423kf44').verify 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/units/test_signature_plain_text.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'oauth/signature/plaintext' 4 | 5 | class TestSignaturePlaintext < Minitest::Test 6 | def test_that_plaintext_implements_plaintext 7 | assert OAuth::Signature.available_methods.include?('plaintext') 8 | end 9 | 10 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature 11 | request = Net::HTTP::Get.new('/photos?file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_token=nnch734d00sl2jdk&oauth_signature=kd94hf93k423kf44%26&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=PLAINTEXT') 12 | 13 | consumer = OAuth::Consumer.new('dpf43f3p2l4k3l03','kd94hf93k423kf44') 14 | token = OAuth::Token.new('nnch734d00sl2jdk', nil) 15 | 16 | assert OAuth::Signature.verify(request, { :consumer => consumer, 17 | :token => token, 18 | :uri => 'http://photos.example.net/photos' } ) 19 | end 20 | 21 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature_part_two 22 | request = Net::HTTP::Get.new('/photos?file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_token=nnch734d00sl2jdk&oauth_signature=kd94hf93k423kf44%26pfkkdhi9sl3r4s00&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=PLAINTEXT') 23 | 24 | consumer = OAuth::Consumer.new('dpf43f3p2l4k3l03','kd94hf93k423kf44') 25 | token = OAuth::Token.new('nnch734d00sl2jdk', 'pfkkdhi9sl3r4s00') 26 | 27 | assert OAuth::Signature.verify(request, { :consumer => consumer, 28 | :token => token, 29 | :uri => 'http://photos.example.net/photos' } ) 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/em_http_request.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/request_proxy/base' 2 | # em-http also uses adddressable so there is no need to require uri. 3 | require 'em-http' 4 | require 'cgi' 5 | 6 | module OAuth::RequestProxy::EventMachine 7 | class HttpRequest < OAuth::RequestProxy::Base 8 | 9 | # A Proxy for use when you need to sign EventMachine::HttpClient instances. 10 | # It needs to be called once the client is construct but before data is sent. 11 | # Also see oauth/client/em-http 12 | proxies ::EventMachine::HttpClient 13 | 14 | # Request in this con 15 | 16 | def method 17 | request.method 18 | end 19 | 20 | def uri 21 | request.normalize_uri.to_s 22 | end 23 | 24 | def parameters 25 | if options[:clobber_request] 26 | options[:parameters] 27 | else 28 | all_parameters 29 | end 30 | end 31 | 32 | protected 33 | 34 | def all_parameters 35 | merged_parameters({}, post_parameters, query_parameters, options[:parameters]) 36 | end 37 | 38 | def query_parameters 39 | CGI.parse(request.normalize_uri.query.to_s) 40 | end 41 | 42 | def post_parameters 43 | headers = request.options[:head] || {} 44 | form_encoded = headers['Content-Type'].to_s.downcase.start_with?("application/x-www-form-urlencoded") 45 | if ['POST', 'PUT'].include?(method) && form_encoded 46 | CGI.parse(request.normalize_body.to_s) 47 | else 48 | {} 49 | end 50 | end 51 | 52 | def merged_parameters(params, *extra_params) 53 | extra_params.compact.each do |params_pairs| 54 | params_pairs.each_pair do |key, value| 55 | if params.has_key?(key) 56 | params[key] += value 57 | else 58 | params[key] = [value].flatten 59 | end 60 | end 61 | end 62 | params 63 | end 64 | 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /test/units/test_rack_request_proxy.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'oauth/request_proxy/rack_request' 4 | 5 | class RackRequestProxyTest < Minitest::Test 6 | 7 | def test_that_proxy_simple_get_request_works 8 | request = Rack::Request.new(Rack::MockRequest.env_for('http://example.com/test?key=value')) 9 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value'}) 10 | 11 | expected_parameters = {'key' => 'value'} 12 | assert_equal expected_parameters, request_proxy.parameters 13 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 14 | assert_equal 'GET', request_proxy.method 15 | end 16 | 17 | def test_that_proxy_simple_post_request_works 18 | request = Rack::Request.new(Rack::MockRequest.env_for('http://example.com/test', :method => 'POST')) 19 | params = {'key' => 'value'} 20 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test', :parameters => params}) 21 | 22 | expected_parameters = {'key' => 'value'} 23 | assert_equal expected_parameters, request_proxy.parameters 24 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 25 | assert_equal 'POST', request_proxy.method 26 | end 27 | 28 | def test_that_proxy_post_and_get_request_works 29 | request = Rack::Request.new(Rack::MockRequest.env_for('http://example.com/test?key=value', :method => 'POST', :input => 'key2=value2')) 30 | params = {'key2' => 'value2'} 31 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value', :parameters => params}) 32 | 33 | expected_parameters = {'key' => 'value', 'key2' => 'value2'} 34 | assert_equal expected_parameters, request_proxy.parameters 35 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 36 | assert_equal 'POST', request_proxy.method 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /test/units/test_action_dispatch_request_proxy.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'oauth/request_proxy/action_dispatch_request' 4 | 5 | class ActionRequestProxyTest < Minitest::Test 6 | 7 | def test_that_proxy_simple_get_request_works 8 | request = ActionDispatch::Request.new(Rack::MockRequest.env_for('http://example.com/test?key=value')) 9 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value'}) 10 | 11 | expected_parameters = {'key' => 'value'} 12 | assert_equal expected_parameters, request_proxy.parameters 13 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 14 | assert_equal 'GET', request_proxy.method 15 | end 16 | 17 | def test_that_proxy_simple_post_request_works 18 | request = ActionDispatch::Request.new(Rack::MockRequest.env_for('http://example.com/test', :method => 'POST')) 19 | params = {'key' => 'value'} 20 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test', :parameters => params}) 21 | 22 | expected_parameters = {'key' => 'value'} 23 | assert_equal expected_parameters, request_proxy.parameters 24 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 25 | assert_equal 'POST', request_proxy.method 26 | end 27 | 28 | def test_that_proxy_post_and_get_request_works 29 | request = ActionDispatch::Request.new(Rack::MockRequest.env_for('http://example.com/test?key=value', :method => 'POST', :input => 'key2=value2')) 30 | params = {'key2' => 'value2'} 31 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value', :parameters => params}) 32 | 33 | expected_parameters = {'key' => 'value', 'key2' => 'value2'} 34 | assert_equal expected_parameters, request_proxy.parameters 35 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 36 | assert_equal 'POST', request_proxy.method 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/net_http.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/request_proxy/base' 2 | require 'net/http' 3 | require 'uri' 4 | require 'cgi' 5 | 6 | module OAuth::RequestProxy::Net 7 | module HTTP 8 | class HTTPRequest < OAuth::RequestProxy::Base 9 | proxies ::Net::HTTPGenericRequest 10 | 11 | def method 12 | request.method 13 | end 14 | 15 | def uri 16 | options[:uri].to_s 17 | end 18 | 19 | def parameters 20 | if options[:clobber_request] 21 | options[:parameters] 22 | else 23 | all_parameters 24 | end 25 | end 26 | 27 | def body 28 | request.body 29 | end 30 | 31 | private 32 | 33 | def all_parameters 34 | request_params = CGI.parse(query_string) 35 | # request_params.each{|k,v| request_params[k] = [nil] if v == []} 36 | 37 | if options[:parameters] 38 | options[:parameters].each do |k,v| 39 | if request_params.has_key?(k) && v 40 | request_params[k] << v 41 | else 42 | request_params[k] = [v] 43 | end 44 | end 45 | end 46 | request_params 47 | end 48 | 49 | def query_string 50 | params = [ query_params, auth_header_params ] 51 | params << post_params if (method.to_s.upcase == 'POST' || method.to_s.upcase == 'PUT') && form_url_encoded? 52 | params.compact.join('&') 53 | end 54 | 55 | def form_url_encoded? 56 | request['Content-Type'] != nil && request['Content-Type'].to_s.downcase.start_with?('application/x-www-form-urlencoded') 57 | end 58 | 59 | def query_params 60 | URI.parse(request.path).query 61 | end 62 | 63 | def post_params 64 | request.body 65 | end 66 | 67 | def auth_header_params 68 | return nil unless request['Authorization'] && request['Authorization'][0,5] == 'OAuth' 69 | request['Authorization'] 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/oauth/client/action_controller_request.rb: -------------------------------------------------------------------------------- 1 | if defined? ActionDispatch 2 | require 'oauth/request_proxy/rack_request' 3 | require 'oauth/request_proxy/action_dispatch_request' 4 | require 'action_dispatch/testing/test_process' 5 | else 6 | require 'oauth/request_proxy/action_controller_request' 7 | require 'action_controller/test_process' 8 | end 9 | 10 | module ActionController 11 | class Base 12 | if defined? ActionDispatch 13 | def process_with_new_base_test(request, response=nil) 14 | request.apply_oauth! if request.respond_to?(:apply_oauth!) 15 | super(request, response) 16 | end 17 | else 18 | def process_with_oauth(request, response=nil) 19 | request.apply_oauth! if request.respond_to?(:apply_oauth!) 20 | process_without_oauth(request, response) 21 | end 22 | alias_method_chain :process, :oauth 23 | end 24 | end 25 | 26 | class TestRequest 27 | def self.use_oauth=(bool) 28 | @use_oauth = bool 29 | end 30 | 31 | def self.use_oauth? 32 | @use_oauth 33 | end 34 | 35 | def configure_oauth(consumer = nil, token = nil, options = {}) 36 | @oauth_options = { :consumer => consumer, 37 | :token => token, 38 | :scheme => 'header', 39 | :signature_method => nil, 40 | :nonce => nil, 41 | :timestamp => nil }.merge(options) 42 | end 43 | 44 | def apply_oauth! 45 | return unless ActionController::TestRequest.use_oauth? && @oauth_options 46 | 47 | @oauth_helper = OAuth::Client::Helper.new(self, @oauth_options.merge(:request_uri => (respond_to?(:fullpath) ? fullpath : request_uri))) 48 | @oauth_helper.amend_user_agent_header(env) 49 | 50 | self.send("set_oauth_#{@oauth_options[:scheme]}") 51 | end 52 | 53 | def set_oauth_header 54 | env['Authorization'] = @oauth_helper.header 55 | end 56 | 57 | def set_oauth_parameters 58 | @query_parameters = @oauth_helper.parameters_with_oauth 59 | @query_parameters.merge!(:oauth_signature => @oauth_helper.signature) 60 | end 61 | 62 | def set_oauth_query_string 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /oauth.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | lib = File.expand_path("../lib/", __FILE__) 4 | $:.unshift lib unless $:.include?(lib) 5 | 6 | require "oauth/version" 7 | 8 | Gem::Specification.new do |spec| 9 | spec.name = "oauth" 10 | spec.version = OAuth::VERSION 11 | spec.license = "MIT" 12 | 13 | spec.authors = ["Pelle Braendgaard", "Blaine Cook", "Larry Halff", "Jesse Clark", "Jon Crosby", "Seth Fitzsimmons", "Matt Sanford", "Aaron Quint"] 14 | spec.email = "oauth-ruby@googlegroupspec.com" 15 | spec.summary = "OAuth Core Ruby implementation" 16 | 17 | spec.executables = ["oauth"] 18 | spec.homepage = "https://github.com/oauth-xx/oauth-ruby" 19 | spec.metadata = { 20 | 'bug_tracker_uri' => "#{spec.homepage}/issues", 21 | 'changelog_uri' => "#{spec.homepage}/blob/master/HISTORY", 22 | 'documentation_uri' => "https://rdoc.info/github/oauth-xx/oauth-ruby/master/frames", 23 | 'homepage_uri' => spec.homepage, 24 | 'source_code_uri' => spec.homepage 25 | } 26 | spec.files = Dir.glob("lib/**/*.rb") 27 | #spec.test_files = Dir.glob("test/**/*.rb") + Dir.glob('test/keys/*') 28 | spec.extra_rdoc_files = [ "LICENSE", "README.rdoc", "TODO" ] 29 | 30 | # This gem will work with 2.0 or greater... 31 | spec.required_ruby_version = '>= 2.0' 32 | 33 | # Nokogiri 1.7 does not accept Ruby 2.0 34 | spec.add_development_dependency("nokogiri", "~> 1.6.8") if RUBY_VERSION < "2.1" 35 | 36 | spec.add_development_dependency("rake") 37 | spec.add_development_dependency("minitest") 38 | spec.add_development_dependency("byebug") 39 | spec.add_development_dependency("actionpack", ">= 5.0") 40 | spec.add_development_dependency("iconv") 41 | spec.add_development_dependency("rack", "~> 2.0") 42 | spec.add_development_dependency("rack-test") 43 | spec.add_development_dependency("mocha", ">= 0.9.12", "<=1.1.0") 44 | spec.add_development_dependency("typhoeus", ">= 0.1.13") 45 | spec.add_development_dependency("em-http-request", "0.2.11") 46 | spec.add_development_dependency("curb") 47 | spec.add_development_dependency("webmock", "< 2.0") 48 | spec.add_development_dependency("codeclimate-test-reporter") 49 | spec.add_development_dependency("simplecov") 50 | spec.add_development_dependency("rest-client") 51 | end 52 | -------------------------------------------------------------------------------- /lib/oauth/cli/authorize_command.rb: -------------------------------------------------------------------------------- 1 | class OAuth::CLI 2 | class AuthorizeCommand < BaseCommand 3 | 4 | def required_options 5 | [:uri] 6 | end 7 | 8 | def _run 9 | request_token = get_request_token 10 | 11 | if request_token.callback_confirmed? 12 | puts "Server appears to support OAuth 1.0a; enabling support." 13 | options[:version] = "1.0a" 14 | end 15 | 16 | puts "Please visit this url to authorize:" 17 | puts request_token.authorize_url 18 | 19 | # parameters for OAuth 1.0a 20 | oauth_verifier = ask_user_for_verifier 21 | 22 | verbosely_get_access_token(request_token, oauth_verifier) 23 | end 24 | 25 | def get_request_token 26 | consumer = get_consumer 27 | scope_options = options[:scope] ? { "scope" => options[:scope] } : {} 28 | consumer.get_request_token({ :oauth_callback => options[:oauth_callback] }, scope_options) 29 | rescue OAuth::Unauthorized => e 30 | alert "A problem occurred while attempting to authorize:" 31 | alert e 32 | alert e.request.body 33 | end 34 | 35 | def get_consumer 36 | OAuth::Consumer.new \ 37 | options[:oauth_consumer_key], 38 | options[:oauth_consumer_secret], 39 | :access_token_url => options[:access_token_url], 40 | :authorize_url => options[:authorize_url], 41 | :request_token_url => options[:request_token_url], 42 | :scheme => options[:scheme], 43 | :http_method => options[:method].to_s.downcase.to_sym 44 | end 45 | 46 | 47 | def ask_user_for_verifier 48 | if options[:version] == "1.0a" 49 | puts "Please enter the verification code provided by the SP (oauth_verifier):" 50 | @stdin.gets.chomp 51 | else 52 | puts "Press return to continue..." 53 | @stdin.gets 54 | nil 55 | end 56 | end 57 | 58 | def verbosely_get_access_token(request_token, oauth_verifier) 59 | access_token = request_token.get_access_token(:oauth_verifier => oauth_verifier) 60 | 61 | puts "Response:" 62 | access_token.params.each do |k,v| 63 | puts " #{k}: #{v}" unless k.is_a?(Symbol) 64 | end 65 | rescue OAuth::Unauthorized => e 66 | alert "A problem occurred while attempting to obtain an access token:" 67 | alert e 68 | alert e.request.body 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /test/cases/spec/1_0-final/test_parameter_encodings.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../oauth_case', __FILE__) 2 | 3 | # See http://oauth.net/core/1.0/#encoding_parameters 4 | # 5 | # 5.1. Parameter Encoding 6 | # 7 | # All parameter names and values are escaped using the [RFC3986] percent-encoding (%xx) mechanism. 8 | # Characters not in the unreserved character set ([RFC3986] section 2.3) MUST be encoded. Characters 9 | # in the unreserved character set MUST NOT be encoded. Hexadecimal characters in encodings MUST be 10 | # upper case. Text names and values MUST be encoded as UTF-8 octets before percent-encoding them per [RFC3629]. 11 | # 12 | # unreserved = ALPHA, DIGIT, '-', '.', '_', '~' 13 | # 14 | 15 | class ParameterEncodingTest < OAuthCase 16 | def test_encodings_alpha_num 17 | assert_encoding 'abcABC123', 'abcABC123' 18 | end 19 | 20 | def test_encodings_non_escaped 21 | assert_encoding '-._~', '-._~' 22 | end 23 | 24 | def test_encodings_percent 25 | assert_encoding '%25', '%' 26 | end 27 | 28 | def test_encodings_plus 29 | assert_encoding '%2B', '+' 30 | end 31 | 32 | def test_encodings_space 33 | assert_encoding '%20', ' ' 34 | end 35 | 36 | def test_encodings_query_param_symbols 37 | assert_encoding '%26%3D%2A', '&=*' 38 | end 39 | 40 | def test_encodings_unicode_lf 41 | assert_encoding '%0A', unicode_to_utf8('U+000A') 42 | end 43 | 44 | def test_encodings_unicode_space 45 | assert_encoding '%20', unicode_to_utf8('U+0020') 46 | end 47 | 48 | def test_encodings_unicode_007f 49 | assert_encoding '%7F', unicode_to_utf8('U+007F') 50 | end 51 | 52 | def test_encodings_unicode_0080 53 | assert_encoding '%C2%80', unicode_to_utf8('U+0080') 54 | end 55 | 56 | def test_encoding_unicode_2708 57 | assert_encoding '%E2%9C%88', unicode_to_utf8('U+2708') 58 | end 59 | 60 | def test_encodings_unicode_3001 61 | assert_encoding '%E3%80%81', unicode_to_utf8('U+3001') 62 | end 63 | 64 | protected 65 | 66 | def unicode_to_utf8(unicode) 67 | return unicode if unicode =~ /\A[[:space:]]*\z/m 68 | 69 | str = '' 70 | 71 | unicode.scan(/(U\+(?:[[:digit:][:xdigit:]]{4,5}|10[[:digit:][:xdigit:]]{4})|.)/mu) do 72 | c = $1 73 | if c =~ /^U\+/ 74 | str << [c[2..-1].hex].pack('U*') 75 | else 76 | str << c 77 | end 78 | end 79 | 80 | str 81 | end 82 | 83 | def assert_encoding(expected, given, message = nil) 84 | assert_equal expected, OAuth::Helper.escape(given), message 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/cases/spec/1_0-final/test_construct_request_url.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../oauth_case', __FILE__) 2 | 3 | # See http://oauth.net/core/1.0/#anchor14 4 | # 5 | #9.1.2. Construct Request URL 6 | # 7 | #The Signature Base String includes the request absolute URL, tying the signature to a specific endpoint. The URL used in the Signature Base String MUST include the scheme, authority, and path, and MUST exclude the query and fragment as defined by [RFC3986] section 3. 8 | # 9 | #If the absolute request URL is not available to the Service Provider (it is always available to the Consumer), it can be constructed by combining the scheme being used, the HTTP Host header, and the relative HTTP request URL. If the Host header is not available, the Service Provider SHOULD use the host name communicated to the Consumer in the documentation or other means. 10 | # 11 | #The Service Provider SHOULD document the form of URL used in the Signature Base String to avoid ambiguity due to URL normalization. Unless specified, URL scheme and authority MUST be lowercase and include the port number; http default port 80 and https default port 443 MUST be excluded. 12 | # 13 | #For example, the request: 14 | # 15 | # HTTP://Example.com:80/resource?id=123 16 | #Is included in the Signature Base String as: 17 | # 18 | # http://example.com/resource 19 | 20 | 21 | class ConstructRequestUrlTest < OAuthCase 22 | 23 | def test_from_spec 24 | assert_request_url("http://example.com/resource","HTTP://Example.com:80/resource?id=123") 25 | end 26 | 27 | def test_simple_url_with_ending_slash 28 | assert_request_url("http://example.com/","http://example.com/") 29 | end 30 | 31 | def test_simple_url_without_ending_slash 32 | assert_request_url("http://example.com/","http://example.com") 33 | end 34 | 35 | def test_of_normalized_http 36 | assert_request_url("http://example.com/resource","http://example.com/resource") 37 | end 38 | 39 | def test_of_https 40 | assert_request_url("https://example.com/resource","HTTPS://Example.com:443/resource?id=123") 41 | end 42 | 43 | def test_of_normalized_https 44 | assert_request_url("https://example.com/resource","https://example.com/resource") 45 | end 46 | 47 | def test_of_http_with_non_standard_port 48 | assert_request_url("http://example.com:8080/resource","http://example.com:8080/resource") 49 | end 50 | 51 | def test_of_https_with_non_standard_port 52 | assert_request_url("https://example.com:8080/resource","https://example.com:8080/resource") 53 | end 54 | 55 | protected 56 | 57 | 58 | def assert_request_url(expected,given,message=nil) 59 | assert_equal expected, request({},'GET',given).normalized_uri, message 60 | end 61 | 62 | end 63 | -------------------------------------------------------------------------------- /lib/oauth/cli/sign_command.rb: -------------------------------------------------------------------------------- 1 | class OAuth::CLI 2 | class SignCommand < BaseCommand 3 | 4 | def required_options 5 | [:oauth_consumer_key, :oauth_consumer_secret, :oauth_token, :oauth_token_secret] 6 | end 7 | 8 | def _run 9 | request = OAuth::RequestProxy.proxy \ 10 | "method" => options[:method], 11 | "uri" => options[:uri], 12 | "parameters" => parameters 13 | 14 | if verbose? 15 | puts_verbose_parameters(request) 16 | end 17 | 18 | request.sign! \ 19 | :consumer_secret => options[:oauth_consumer_secret], 20 | :token_secret => options[:oauth_token_secret] 21 | 22 | if verbose? 23 | puts_verbose_request(request) 24 | else 25 | puts request.oauth_signature 26 | end 27 | end 28 | 29 | def puts_verbose_parameters(request) 30 | puts "OAuth parameters:" 31 | request.oauth_parameters.each do |k,v| 32 | puts " " + [k, v] * ": " 33 | end 34 | puts 35 | 36 | if request.non_oauth_parameters.any? 37 | puts "Parameters:" 38 | request.non_oauth_parameters.each do |k,v| 39 | puts " " + [k, v] * ": " 40 | end 41 | puts 42 | end 43 | end 44 | 45 | def puts_verbose_request(request) 46 | puts "Method: #{request.method}" 47 | puts "URI: #{request.uri}" 48 | puts "Normalized params: #{request.normalized_parameters}" unless options[:xmpp] 49 | puts "Signature base string: #{request.signature_base_string}" 50 | 51 | if xmpp? 52 | puts 53 | puts "XMPP Stanza:" 54 | puts xmpp_output(request) 55 | puts 56 | puts "Note: You may want to use bare JIDs in your URI." 57 | puts 58 | else 59 | puts "OAuth Request URI: #{request.signed_uri}" 60 | puts "Request URI: #{request.signed_uri(false)}" 61 | puts "Authorization header: #{request.oauth_header(:realm => options[:realm])}" 62 | end 63 | puts "Signature: #{request.oauth_signature}" 64 | puts "Escaped signature: #{OAuth::Helper.escape(request.oauth_signature)}" 65 | end 66 | 67 | def xmpp_output(request) 68 | <<-EOS 69 | 70 | #{request.oauth_consumer_key} 71 | #{request.oauth_token} 72 | #{request.oauth_signature_method} 73 | #{request.oauth_signature} 74 | #{request.oauth_timestamp} 75 | #{request.oauth_nonce} 76 | #{request.oauth_version} 77 | 78 | EOS 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/oauth/signature/base.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/signature' 2 | require 'oauth/helper' 3 | require 'oauth/request_proxy/base' 4 | require 'base64' 5 | 6 | module OAuth::Signature 7 | class Base 8 | include OAuth::Helper 9 | 10 | attr_accessor :options 11 | attr_reader :token_secret, :consumer_secret, :request 12 | 13 | def self.implements(signature_method = nil) 14 | return @implements if signature_method.nil? 15 | @implements = signature_method 16 | OAuth::Signature.available_methods[@implements] = self 17 | end 18 | 19 | def initialize(request, options = {}, &block) 20 | raise TypeError unless request.kind_of?(OAuth::RequestProxy::Base) 21 | @request = request 22 | @options = options 23 | 24 | ## consumer secret was determined beforehand 25 | 26 | @consumer_secret = options[:consumer].secret if options[:consumer] 27 | 28 | # presence of :consumer_secret option will override any Consumer that's provided 29 | @consumer_secret = options[:consumer_secret] if options[:consumer_secret] 30 | 31 | ## token secret was determined beforehand 32 | 33 | @token_secret = options[:token].secret if options[:token] 34 | 35 | # presence of :token_secret option will override any Token that's provided 36 | @token_secret = options[:token_secret] if options[:token_secret] 37 | 38 | # override secrets based on the values returned from the block (if any) 39 | if block_given? 40 | # consumer secret and token secret need to be looked up based on pieces of the request 41 | secrets = yield block.arity == 1 ? request : [token, consumer_key, nonce, request.timestamp] 42 | if secrets.is_a?(Array) && secrets.size == 2 43 | @token_secret = secrets[0] 44 | @consumer_secret = secrets[1] 45 | end 46 | end 47 | end 48 | 49 | def signature 50 | Base64.encode64(digest).chomp.gsub(/\n/,'') 51 | end 52 | 53 | def ==(cmp_signature) 54 | signature == cmp_signature 55 | end 56 | 57 | def verify 58 | self == self.request.signature 59 | end 60 | 61 | def signature_base_string 62 | request.signature_base_string 63 | end 64 | 65 | def body_hash 66 | raise_instantiation_error 67 | end 68 | 69 | private 70 | 71 | def token 72 | request.token 73 | end 74 | 75 | def consumer_key 76 | request.consumer_key 77 | end 78 | 79 | def nonce 80 | request.nonce 81 | end 82 | 83 | def secret 84 | "#{escape(consumer_secret)}&#{escape(token_secret)}" 85 | end 86 | 87 | def digest 88 | raise_instantiation_error 89 | end 90 | 91 | def raise_instantiation_error 92 | raise NotImplementedError, "Cannot instantiate #{self.class.name} class directly." 93 | end 94 | 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/action_controller_request.rb: -------------------------------------------------------------------------------- 1 | require 'active_support' 2 | require "active_support/version" 3 | require 'action_controller' 4 | require 'uri' 5 | 6 | if 7 | Gem::Version.new(ActiveSupport::VERSION::STRING) < Gem::Version.new("3") 8 | then # rails 2.x 9 | require 'action_controller/request' 10 | unless ActionController::Request::HTTP_METHODS.include?("patch") 11 | ActionController::Request::HTTP_METHODS << "patch" 12 | ActionController::Request::HTTP_METHOD_LOOKUP["PATCH"] = :patch 13 | ActionController::Request::HTTP_METHOD_LOOKUP["patch"] = :patch 14 | end 15 | 16 | elsif 17 | Gem::Version.new(ActiveSupport::VERSION::STRING) < Gem::Version.new("4") 18 | then # rails 3.x 19 | require 'action_dispatch/http/request' 20 | unless ActionDispatch::Request::HTTP_METHODS.include?("patch") 21 | ActionDispatch::Request::HTTP_METHODS << "patch" 22 | ActionDispatch::Request::HTTP_METHOD_LOOKUP["PATCH"] = :patch 23 | ActionDispatch::Request::HTTP_METHOD_LOOKUP["patch"] = :patch 24 | end 25 | 26 | else # rails 4.x and later - already has patch 27 | require 'action_dispatch/http/request' 28 | end 29 | 30 | module OAuth::RequestProxy 31 | class ActionControllerRequest < OAuth::RequestProxy::Base 32 | proxies(defined?(ActionDispatch::AbstractRequest) ? ActionDispatch::AbstractRequest : ActionDispatch::Request) 33 | 34 | def method 35 | request.method.to_s.upcase 36 | end 37 | 38 | def uri 39 | request.url 40 | end 41 | 42 | def parameters 43 | if options[:clobber_request] 44 | options[:parameters] || {} 45 | else 46 | params = request_params.merge(query_params).merge(header_params) 47 | params.stringify_keys! if params.respond_to?(:stringify_keys!) 48 | params.merge(options[:parameters] || {}) 49 | end 50 | end 51 | 52 | # Override from OAuth::RequestProxy::Base to avoid roundtrip 53 | # conversion to Hash or Array and thus preserve the original 54 | # parameter names 55 | def parameters_for_signature 56 | params = [] 57 | params << options[:parameters].to_query if options[:parameters] 58 | 59 | unless options[:clobber_request] 60 | params << header_params.to_query 61 | params << request.query_string unless query_string_blank? 62 | 63 | if request.post? && request.content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded") 64 | params << request.raw_post 65 | end 66 | end 67 | 68 | params. 69 | join('&').split('&'). 70 | reject { |s| s.match(/\A\s*\z/) }. 71 | map { |p| p.split('=').map{|esc| CGI.unescape(esc)} }. 72 | reject { |kv| kv[0] == 'oauth_signature'} 73 | end 74 | 75 | protected 76 | 77 | def query_params 78 | request.query_parameters 79 | end 80 | 81 | def request_params 82 | request.request_parameters 83 | end 84 | 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/units/test_request_token.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class StubbedToken < OAuth::RequestToken 4 | define_method :build_url_promoted do |root_domain, params| 5 | build_url root_domain, params 6 | end 7 | end 8 | 9 | class TestRequestToken < Minitest::Test 10 | def setup 11 | # setup a fake req. token. mocking Consumer would be more appropriate... 12 | @request_token = OAuth::RequestToken.new( 13 | OAuth::Consumer.new("key", "secret", {}), 14 | "key", 15 | "secret" 16 | ) 17 | end 18 | 19 | def test_request_token_builds_authorize_url_connectly_with_additional_params 20 | auth_url = @request_token.authorize_url({:oauth_callback => "github.com"}) 21 | assert auth_url 22 | assert_match(/oauth_token/, auth_url) 23 | assert_match(/oauth_callback/, auth_url) 24 | end 25 | 26 | def test_request_token_builds_authorize_url_connectly_with_no_or_nil_params 27 | # we should only have 1 key in the url returned if we didn't pass anything. 28 | # this is the only required param to authenticate the client. 29 | auth_url = @request_token.authorize_url(nil) 30 | assert auth_url 31 | assert_match(/\?oauth_token=/, auth_url) 32 | 33 | auth_url = @request_token.authorize_url 34 | assert auth_url 35 | assert_match(/\?oauth_token=/, auth_url) 36 | end 37 | 38 | def test_request_token_returns_nil_authorize_url_when_token_is_nil 39 | @request_token.token = nil 40 | assert_nil @request_token.authorize_url 41 | end 42 | 43 | def test_request_token_builds_authenticate_url_connectly_with_additional_params 44 | authenticate_url = @request_token.authenticate_url({:oauth_callback => "github.com"}) 45 | assert authenticate_url 46 | assert_match(/oauth_token/, authenticate_url) 47 | assert_match(/oauth_callback/, authenticate_url) 48 | end 49 | 50 | def test_request_token_builds_authenticate_url_connectly_with_no_or_nil_params 51 | # we should only have 1 key in the url returned if we didn't pass anything. 52 | # this is the only required param to authenticate the client. 53 | authenticate_url = @request_token.authenticate_url(nil) 54 | assert authenticate_url 55 | assert_match(/\?oauth_token=/, authenticate_url) 56 | 57 | authenticate_url2 = @request_token.authenticate_url 58 | assert authenticate_url2 59 | assert_match(/\?oauth_token=/, authenticate_url2) 60 | end 61 | 62 | def test_request_token_returns_nil_authenticate_url_when_token_is_nil 63 | @request_token.token = nil 64 | assert_nil @request_token.authenticate_url 65 | end 66 | 67 | #TODO: mock out the Consumer to test the Consumer/AccessToken interaction. 68 | def test_get_access_token 69 | end 70 | 71 | def test_build_url 72 | @stubbed_token = StubbedToken.new(nil, nil, nil) 73 | assert_respond_to @stubbed_token, :build_url_promoted 74 | url = @stubbed_token.build_url_promoted( 75 | "http://github.com/oauth/authorize", 76 | {:foo => "bar bar"}) 77 | assert url 78 | assert_equal "http://github.com/oauth/authorize?foo=bar+bar", url 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/cases/spec/1_0-final/test_signature_base_strings.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../oauth_case', __FILE__) 2 | 3 | # See http://oauth.net/core/1.0/#anchor14 4 | # 5 | # 9.1. Signature Base String 6 | # 7 | # The Signature Base String is a consistent reproducible concatenation of the request elements 8 | # into a single string. The string is used as an input in hashing or signing algorithms. The 9 | # HMAC-SHA1 signature method provides both a standard and an example of using the Signature 10 | # Base String with a signing algorithm to generate signatures. All the request parameters MUST 11 | # be encoded as described in Parameter Encoding prior to constructing the Signature Base String. 12 | # 13 | 14 | class SignatureBaseStringTest < OAuthCase 15 | 16 | def test_A_5_1 17 | parameters={ 18 | 'oauth_consumer_key'=>'dpf43f3p2l4k3l03', 19 | 'oauth_token'=>'nnch734d00sl2jdk', 20 | 'oauth_signature_method'=>'HMAC-SHA1', 21 | 'oauth_timestamp'=>'1191242096', 22 | 'oauth_nonce'=>'kllo9940pd9333jh', 23 | 'oauth_version'=>'1.0', 24 | 'file'=>'vacation.jpg', 25 | 'size'=>'original' 26 | } 27 | sbs='GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal' 28 | 29 | assert_signature_base_string sbs,parameters,'GET',"http://photos.example.net/photos" 30 | end 31 | 32 | # These are from the wiki http://wiki.oauth.net/TestCases 33 | # in the section Concatenate Test Elements 34 | 35 | def test_wiki_1_simple_with_ending_slash 36 | parameters={ 37 | 'n'=>'v' 38 | } 39 | sbs='GET&http%3A%2F%2Fexample.com%2F&n%3Dv' 40 | 41 | assert_signature_base_string sbs,parameters,'GET',"http://example.com/" 42 | end 43 | 44 | 45 | def test_wiki_2_simple_without_ending_slash 46 | parameters={ 47 | 'n'=>'v' 48 | } 49 | sbs='GET&http%3A%2F%2Fexample.com%2F&n%3Dv' 50 | 51 | assert_signature_base_string sbs,parameters,'GET',"http://example.com" 52 | end 53 | 54 | def test_wiki_2_request_token 55 | parameters={ 56 | 'oauth_version'=>'1.0', 57 | 'oauth_consumer_key'=>'dpf43f3p2l4k3l03', 58 | 'oauth_timestamp'=>'1191242090', 59 | 'oauth_nonce'=>'hsu94j3884jdopsl', 60 | 'oauth_signature_method'=>'PLAINTEXT', 61 | 'oauth_signature'=>'ignored' } 62 | sbs='POST&https%3A%2F%2Fphotos.example.net%2Frequest_token&oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dhsu94j3884jdopsl%26oauth_signature_method%3DPLAINTEXT%26oauth_timestamp%3D1191242090%26oauth_version%3D1.0' 63 | 64 | assert_signature_base_string sbs,parameters,'POST',"https://photos.example.net/request_token" 65 | end 66 | 67 | protected 68 | 69 | 70 | def assert_signature_base_string(expected,params={},method='GET',uri="http://photos.example.net/photos",message="Signature Base String does not match") 71 | assert_equal expected, signature_base_string(params,method,uri), message 72 | end 73 | 74 | def signature_base_string(params={},method='GET',uri="http://photos.example.net/photos") 75 | request(params,method,uri).signature_base_string 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /test/cases/spec/1_0-final/test_normalize_request_parameters.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../oauth_case', __FILE__) 2 | 3 | # See http://oauth.net/core/1.0/#anchor14 4 | # 5 | # 9.1.1. Normalize Request Parameters 6 | # 7 | # The request parameters are collected, sorted and concatenated into a normalized string: 8 | # 9 | # Parameters in the OAuth HTTP Authorization header excluding the realm parameter. 10 | # Parameters in the HTTP POST request body (with a content-type of application/x-www-form-urlencoded). 11 | # HTTP GET parameters added to the URLs in the query part (as defined by [RFC3986] section 3). 12 | # The oauth_signature parameter MUST be excluded. 13 | # 14 | # The parameters are normalized into a single string as follows: 15 | # 16 | # Parameters are sorted by name, using lexicographical byte value ordering. 17 | # If two or more parameters share the same name, they are sorted by their value. For example: 18 | # 19 | # a=1, c=hi%20there, f=25, f=50, f=a, z=p, z=t 20 | # Parameters are concatenated in their sorted order into a single string. For each parameter, 21 | # the name is separated from the corresponding value by an ‘=’ character (ASCII code 61), even 22 | # if the value is empty. Each name-value pair is separated by an ‘&’ character (ASCII code 38). For example: 23 | # a=1&c=hi%20there&f=25&f=50&f=a&z=p&z=t 24 | # 25 | 26 | 27 | class NormalizeRequestParametersTest < OAuthCase 28 | 29 | def test_parameters_for_signature 30 | params={"a"=>1, "c"=>"hi there", "f"=>"a", "z"=>"t"} 31 | assert_equal params,request(params).parameters_for_signature 32 | end 33 | 34 | 35 | def test_parameters_for_signature_removes_oauth_signature 36 | params={"a"=>1, "c"=>"hi there", "f"=>"a", "z"=>"t"} 37 | assert_equal params,request(params.merge({'oauth_signature'=>'blalbla'})).parameters_for_signature 38 | end 39 | 40 | def test_spec_example 41 | assert_normalized 'a=1&c=hi%20there&f=25&f=50&f=a&z=p&z=t', { 'a' => 1, 'c' => 'hi there', 'f' => ['25', '50', 'a'], 'z' => ['p', 't'] } 42 | end 43 | 44 | def test_sorts_parameters_correctly 45 | # values for 'f' are scrambled 46 | assert_normalized 'a=1&c=hi%20there&f=5&f=70&f=a&z=p&z=t', { 'a' => 1, 'c' => 'hi there', 'f' => ['a', '70', '5'], 'z' => ['p', 't'] } 47 | end 48 | 49 | def test_empty 50 | assert_normalized "",{} 51 | end 52 | 53 | 54 | # These are from the wiki http://wiki.oauth.net/TestCases 55 | # in the section Normalize Request Parameters 56 | # Parameters have already been x-www-form-urlencoded (i.e. + = ) 57 | def test_wiki1 58 | assert_normalized "name=",{"name"=>nil} 59 | end 60 | 61 | def test_wiki2 62 | assert_normalized "a=b",{'a'=>'b'} 63 | end 64 | 65 | def test_wiki3 66 | assert_normalized "a=b&c=d",{'a'=>'b','c'=>'d'} 67 | end 68 | 69 | def test_wiki4 70 | assert_normalized "a=x%20y&a=x%21y",{'a'=>["x!y","x y"]} 71 | 72 | end 73 | 74 | def test_wiki5 75 | assert_normalized "x=a&x%21y=a",{"x!y"=>'a','x'=>'a'} 76 | end 77 | 78 | protected 79 | 80 | 81 | def assert_normalized(expected,params,message=nil) 82 | assert_equal expected, normalize_request_parameters(params), message 83 | end 84 | 85 | def normalize_request_parameters(params={}) 86 | request(params).normalized_parameters 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /test/units/test_curb_request_proxy.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | begin 4 | 5 | require 'oauth/request_proxy/curb_request' 6 | require 'curb' 7 | 8 | 9 | class CurbRequestProxyTest < Minitest::Test 10 | 11 | def test_that_proxy_simple_get_request_works 12 | request = Curl::Easy.new('/test?key=value') 13 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value'}) 14 | 15 | expected_parameters = {'key' => ['value']} 16 | assert_equal expected_parameters, request_proxy.parameters_for_signature 17 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 18 | end 19 | 20 | def test_that_proxy_simple_post_request_works_with_arguments 21 | request = Curl::Easy.new('/test') 22 | params = {'key' => 'value'} 23 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test', :parameters => params}) 24 | 25 | expected_parameters = {'key' => 'value'} 26 | assert_equal expected_parameters, request_proxy.parameters_for_signature 27 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 28 | end 29 | 30 | def test_that_proxy_simple_post_request_works_with_form_data 31 | request = Curl::Easy.new('/test') 32 | request.post_body = 'key=value' 33 | request.headers['Content-Type'] = 'application/x-www-form-urlencoded' 34 | 35 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test'}) 36 | 37 | expected_parameters = {'key' => 'value'} 38 | assert_equal expected_parameters, request_proxy.parameters_for_signature 39 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 40 | end 41 | 42 | def test_that_proxy_simple_put_request_works_with_arguments 43 | request = Curl::Easy.new('/test') 44 | params = {'key' => 'value'} 45 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test', :parameters => params}) 46 | 47 | expected_parameters = {'key' => 'value'} 48 | assert_equal expected_parameters, request_proxy.parameters_for_signature 49 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 50 | end 51 | 52 | def test_that_proxy_simple_put_request_works_with_form_data 53 | request = Curl::Easy.new('/test') 54 | request.post_body = 'key=value' 55 | 56 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test'}) 57 | 58 | expected_parameters = {} 59 | assert_equal expected_parameters, request_proxy.parameters_for_signature 60 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 61 | end 62 | 63 | def test_that_proxy_post_request_works_with_mixed_parameter_sources 64 | request = Curl::Easy.new('/test?key=value') 65 | request.post_body = 'key2=value2' 66 | request.headers['Content-Type'] = 'application/x-www-form-urlencoded' 67 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value', :parameters => {'key3' => 'value3'}}) 68 | 69 | expected_parameters = {'key' => ['value'], 'key2' => 'value2', 'key3' => 'value3'} 70 | assert_equal expected_parameters, request_proxy.parameters_for_signature 71 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 72 | end 73 | end 74 | 75 | rescue LoadError => e 76 | warn "! problems loading curb, skipping these tests: #{e}" 77 | end 78 | -------------------------------------------------------------------------------- /test/units/test_net_http_request_proxy.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class NetHTTPRequestProxyTest < Minitest::Test 4 | 5 | def test_that_proxy_simple_get_request_works 6 | request = Net::HTTP::Get.new('/test?key=value') 7 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value'}) 8 | 9 | expected_parameters = {'key' => ['value']} 10 | assert_equal expected_parameters, request_proxy.parameters_for_signature 11 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 12 | assert_equal 'GET', request_proxy.method 13 | end 14 | 15 | def test_that_proxy_simple_post_request_works_with_arguments 16 | request = Net::HTTP::Post.new('/test') 17 | params = {'key' => 'value'} 18 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test', :parameters => params}) 19 | 20 | expected_parameters = {'key' => ['value']} 21 | assert_equal expected_parameters, request_proxy.parameters_for_signature 22 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 23 | assert_equal 'POST', request_proxy.method 24 | end 25 | 26 | def test_that_proxy_simple_post_request_works_with_form_data 27 | request = Net::HTTP::Post.new('/test') 28 | params = {'key' => 'value'} 29 | request.set_form_data(params) 30 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test'}) 31 | 32 | expected_parameters = {'key' => ['value']} 33 | assert_equal expected_parameters, request_proxy.parameters_for_signature 34 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 35 | assert_equal 'POST', request_proxy.method 36 | end 37 | 38 | def test_that_proxy_simple_put_request_works_with_argugments 39 | request = Net::HTTP::Put.new('/test') 40 | params = {'key' => 'value'} 41 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test', :parameters => params}) 42 | 43 | expected_parameters = {'key' => ['value']} 44 | assert_equal expected_parameters, request_proxy.parameters_for_signature 45 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 46 | assert_equal 'PUT', request_proxy.method 47 | end 48 | 49 | def test_that_proxy_simple_put_request_works_with_form_data 50 | request = Net::HTTP::Put.new('/test') 51 | params = {'key' => 'value'} 52 | request.set_form_data(params) 53 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test'}) 54 | 55 | expected_parameters = {'key' => ['value']} 56 | assert_equal expected_parameters, request_proxy.parameters_for_signature 57 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 58 | assert_equal 'PUT', request_proxy.method 59 | end 60 | 61 | def test_that_proxy_post_request_uses_post_parameters 62 | request = Net::HTTP::Post.new('/test?key=value') 63 | request.set_form_data({'key2' => 'value2'}) 64 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value', :parameters => {'key3' => 'value3'}}) 65 | 66 | expected_parameters = {'key' => ['value'], 'key2' => ['value2'], 'key3' => ['value3']} 67 | assert_equal expected_parameters, request_proxy.parameters_for_signature 68 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 69 | assert_equal 'POST', request_proxy.method 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Ruby OAuth 2 | 3 | == Status 4 | 5 | {Build Status}[https://travis-ci.org/oauth-xx/oauth-ruby] 6 | 7 | 8 | 9 | == What 10 | 11 | This is a RubyGem for implementing both OAuth clients and servers in Ruby applications. 12 | 13 | See the OAuth specs http://oauth.net/core/1.0/ 14 | 15 | == Installing 16 | 17 | sudo gem install oauth 18 | 19 | The source code is now hosted on the OAuth GitHub Project http://github.com/oauth-xx/oauth-ruby 20 | 21 | == The basics 22 | 23 | This is a ruby library which is intended to be used in creating Ruby Consumer and Service Provider applications. It is NOT a Rails plugin, but could easily be used for the foundation for such a Rails plugin. 24 | 25 | As a matter of fact it has been pulled out from an OAuth Rails GEM (https://rubygems.org/gems/oauth-plugin https://github.com/pelle/oauth-plugin) which now uses this gem as a dependency. 26 | 27 | == Demonstration of usage 28 | 29 | We need to specify the oauth_callback url explicitly, otherwise it defaults to "oob" (Out of Band) 30 | 31 | callback_url = "http://127.0.0.1:3000/oauth/callback" 32 | 33 | Create a new `OAuth::Consumer` instance by passing it a configuration hash: 34 | 35 | oauth_consumer = OAuth::Consumer.new("key", "secret", :site => "https://agree2") 36 | 37 | Start the process by requesting a token 38 | 39 | request_token = oauth_consumer.get_request_token(:oauth_callback => callback_url) 40 | 41 | session[:token] = request_token.token 42 | session[:token_secret] = request_token.secret 43 | redirect_to request_token.authorize_url(:oauth_callback => callback_url) 44 | 45 | When user returns create an access_token 46 | 47 | hash = { oauth_token: session[:token], oauth_token_secret: session[:token_secret]} 48 | request_token = OAuth::RequestToken.from_hash(oauth_consumer, hash) 49 | access_token = request_token.get_access_token 50 | # For 3-legged authorization, flow oauth_verifier is passed as param in callback 51 | # access_token = request_token.get_access_token(oauth_verifier: params[:oauth_verifier]) 52 | @photos = access_token.get('/photos.xml') 53 | 54 | Now that you have an access token, you can use Typhoeus to interact with the OAuth provider if you choose. 55 | 56 | require 'typhoeus' 57 | require 'oauth/request_proxy/typhoeus_request' 58 | oauth_params = {:consumer => oauth_consumer, :token => access_token} 59 | hydra = Typhoeus::Hydra.new 60 | req = Typhoeus::Request.new(uri, options) # :method needs to be specified in options 61 | oauth_helper = OAuth::Client::Helper.new(req, oauth_params.merge(:request_uri => uri)) 62 | req.options[:headers].merge!({"Authorization" => oauth_helper.header}) # Signs the request 63 | hydra.queue(req) 64 | hydra.run 65 | @response = req.response 66 | 67 | 68 | == More Information 69 | 70 | * RDoc: http://rdoc.info/github/oauth-xx/oauth-ruby/master/frames 71 | * Mailing List/Google Group: http://groups.google.com/group/oauth-ruby 72 | 73 | == How to submit patches 74 | 75 | The source code is now hosted on the OAuth GitHub Project http://github.com/oauth-xx/oauth-ruby 76 | 77 | To submit a patch, please fork the oauth project and create a patch with tests. Once you're happy with it send a pull request and post a message to the google group. 78 | 79 | == License 80 | 81 | This code is free to use under the terms of the MIT license. 82 | 83 | == Contact 84 | 85 | OAuth Ruby has been created and maintained by a large number of talented individuals. 86 | The current maintainer is Aaron Quint (quirkey). 87 | 88 | Comments are welcome. Send an email to via the OAuth Ruby mailing list http://groups.google.com/group/oauth-ruby 89 | -------------------------------------------------------------------------------- /lib/oauth/client/helper.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/client' 2 | require 'oauth/consumer' 3 | require 'oauth/helper' 4 | require 'oauth/token' 5 | require 'oauth/signature/hmac/sha1' 6 | 7 | module OAuth::Client 8 | class Helper 9 | include OAuth::Helper 10 | 11 | def initialize(request, options = {}) 12 | @request = request 13 | @options = options 14 | @options[:signature_method] ||= 'HMAC-SHA1' 15 | end 16 | 17 | def options 18 | @options 19 | end 20 | 21 | def nonce 22 | options[:nonce] ||= generate_key 23 | end 24 | 25 | def timestamp 26 | options[:timestamp] ||= generate_timestamp 27 | end 28 | 29 | def oauth_parameters 30 | out = { 31 | 'oauth_body_hash' => options[:body_hash], 32 | 'oauth_callback' => options[:oauth_callback], 33 | 'oauth_consumer_key' => options[:consumer].key, 34 | 'oauth_token' => options[:token] ? options[:token].token : '', 35 | 'oauth_signature_method' => options[:signature_method], 36 | 'oauth_timestamp' => timestamp, 37 | 'oauth_nonce' => nonce, 38 | 'oauth_verifier' => options[:oauth_verifier], 39 | 'oauth_version' => (options[:oauth_version] || '1.0'), 40 | 'oauth_session_handle' => options[:oauth_session_handle] 41 | } 42 | allowed_empty_params = options[:allow_empty_params] 43 | if allowed_empty_params != true && !allowed_empty_params.kind_of?(Array) 44 | allowed_empty_params = allowed_empty_params == false ? [] : [allowed_empty_params] 45 | end 46 | out.select! { |k,v| v.to_s != '' || allowed_empty_params == true || allowed_empty_params.include?(k) } 47 | out 48 | end 49 | 50 | def signature(extra_options = {}) 51 | OAuth::Signature.sign(@request, { :uri => options[:request_uri], 52 | :consumer => options[:consumer], 53 | :token => options[:token], 54 | :unsigned_parameters => options[:unsigned_parameters] 55 | }.merge(extra_options) ) 56 | end 57 | 58 | def signature_base_string(extra_options = {}) 59 | OAuth::Signature.signature_base_string(@request, { :uri => options[:request_uri], 60 | :consumer => options[:consumer], 61 | :token => options[:token], 62 | :parameters => oauth_parameters}.merge(extra_options) ) 63 | end 64 | 65 | def token_request? 66 | @options[:token_request].eql?(true) 67 | end 68 | 69 | def hash_body 70 | @options[:body_hash] = OAuth::Signature.body_hash(@request, :parameters => oauth_parameters) 71 | end 72 | 73 | def amend_user_agent_header(headers) 74 | @oauth_ua_string ||= "OAuth gem v#{OAuth::VERSION}" 75 | # Net::HTTP in 1.9 appends Ruby 76 | if headers['User-Agent'] && headers['User-Agent'] != 'Ruby' 77 | headers['User-Agent'] += " (#{@oauth_ua_string})" 78 | else 79 | headers['User-Agent'] = @oauth_ua_string 80 | end 81 | end 82 | 83 | def header 84 | parameters = oauth_parameters 85 | parameters.merge!('oauth_signature' => signature(options.merge(:parameters => parameters))) 86 | 87 | header_params_str = parameters.sort.map { |k,v| "#{k}=\"#{escape(v)}\"" }.join(', ') 88 | 89 | realm = "realm=\"#{options[:realm]}\", " if options[:realm] 90 | "OAuth #{realm}#{header_params_str}" 91 | end 92 | 93 | def parameters 94 | OAuth::RequestProxy.proxy(@request).parameters 95 | end 96 | 97 | def parameters_with_oauth 98 | oauth_parameters.merge(parameters) 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/oauth/tokens/access_token.rb: -------------------------------------------------------------------------------- 1 | module OAuth 2 | # The Access Token is used for the actual "real" web service calls that you perform against the server 3 | class AccessToken < ConsumerToken 4 | # The less intrusive way. Otherwise, if we are to do it correctly inside consumer, 5 | # we need to restructure and touch more methods: request(), sign!(), etc. 6 | def request(http_method, path, *arguments) 7 | request_uri = URI.parse(path) 8 | site_uri = consumer.uri 9 | is_service_uri_different = (request_uri.absolute? && request_uri != site_uri) 10 | begin 11 | consumer.uri(request_uri) if is_service_uri_different 12 | @response = super(http_method, path, *arguments) 13 | ensure 14 | # NOTE: reset for wholesomeness? meaning that we admit only AccessToken service calls may use different URIs? 15 | # so reset in case consumer is still used for other token-management tasks subsequently? 16 | consumer.uri(site_uri) if is_service_uri_different 17 | end 18 | @response 19 | end 20 | 21 | # Make a regular GET request using AccessToken 22 | # 23 | # @response = @token.get('/people') 24 | # @response = @token.get('/people', { 'Accept'=>'application/xml' }) 25 | # 26 | def get(path, headers = {}) 27 | request(:get, path, headers) 28 | end 29 | 30 | # Make a regular HEAD request using AccessToken 31 | # 32 | # @response = @token.head('/people') 33 | # 34 | def head(path, headers = {}) 35 | request(:head, path, headers) 36 | end 37 | 38 | # Make a regular POST request using AccessToken 39 | # 40 | # @response = @token.post('/people') 41 | # @response = @token.post('/people', { :name => 'Bob', :email => 'bob@mailinator.com' }) 42 | # @response = @token.post('/people', { :name => 'Bob', :email => 'bob@mailinator.com' }, { 'Accept' => 'application/xml' }) 43 | # @response = @token.post('/people', nil, {'Accept' => 'application/xml' }) 44 | # @response = @token.post('/people', @person.to_xml, { 'Accept'=>'application/xml', 'Content-Type' => 'application/xml' }) 45 | # 46 | def post(path, body = '', headers = {}) 47 | request(:post, path, body, headers) 48 | end 49 | 50 | # Make a regular PUT request using AccessToken 51 | # 52 | # @response = @token.put('/people/123') 53 | # @response = @token.put('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' }) 54 | # @response = @token.put('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' }, { 'Accept' => 'application/xml' }) 55 | # @response = @token.put('/people/123', nil, { 'Accept' => 'application/xml' }) 56 | # @response = @token.put('/people/123', @person.to_xml, { 'Accept' => 'application/xml', 'Content-Type' => 'application/xml' }) 57 | # 58 | def put(path, body = '', headers = {}) 59 | request(:put, path, body, headers) 60 | end 61 | 62 | # Make a regular PATCH request using AccessToken 63 | # 64 | # @response = @token.patch('/people/123') 65 | # @response = @token.patch('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' }) 66 | # @response = @token.patch('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' }, { 'Accept' => 'application/xml' }) 67 | # @response = @token.patch('/people/123', nil, { 'Accept' => 'application/xml' }) 68 | # @response = @token.patch('/people/123', @person.to_xml, { 'Accept' => 'application/xml', 'Content-Type' => 'application/xml' }) 69 | # 70 | def patch(path, body = '', headers = {}) 71 | request(:patch, path, body, headers) 72 | end 73 | 74 | # Make a regular DELETE request using AccessToken 75 | # 76 | # @response = @token.delete('/people/123') 77 | # @response = @token.delete('/people/123', { 'Accept' => 'application/xml' }) 78 | # 79 | def delete(path, headers = {}) 80 | request(:delete, path, headers) 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /test/units/test_em_http_client.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | begin 3 | 4 | require 'oauth/client/em_http' 5 | 6 | class EmHttpClientTest < Minitest::Test 7 | 8 | def setup 9 | @consumer = OAuth::Consumer.new('consumer_key_86cad9', '5888bf0345e5d237') 10 | @token = OAuth::Token.new('token_411a7f', '3196ffd991c8ebdb') 11 | @request_uri = URI.parse('http://example.com/test?key=value') 12 | @request_parameters = { 'key' => 'value' } 13 | @nonce = 225579211881198842005988698334675835446 14 | @timestamp = "1199645624" 15 | # This is really unneeded I guess. 16 | @http = Net::HTTP.new(@request_uri.host, @request_uri.port) 17 | end 18 | 19 | def test_that_using_auth_headers_on_get_requests_works 20 | request = create_client 21 | request.oauth!(@http, @consumer, @token, {:nonce => @nonce, :timestamp => @timestamp}) 22 | 23 | assert_equal 'GET', request.method 24 | assert_equal '/test', request.normalize_uri.path 25 | assert_equal "key=value", request.normalize_uri.query 26 | assert_equal_authz_headers "OAuth oauth_nonce=\"225579211881198842005988698334675835446\", oauth_signature_method=\"HMAC-SHA1\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"1oO2izFav1GP4kEH2EskwXkCRFg%3D\", oauth_version=\"1.0\"", authz_header(request) 27 | end 28 | 29 | def test_that_using_auth_headers_on_get_requests_works_with_plaintext 30 | require 'oauth/signature/plaintext' 31 | c = OAuth::Consumer.new('consumer_key_86cad9', '5888bf0345e5d237',{ 32 | :signature_method => 'PLAINTEXT' 33 | }) 34 | request = create_client 35 | request.oauth!(@http, c, @token, {:nonce => @nonce, :timestamp => @timestamp, :signature_method => 'PLAINTEXT'}) 36 | 37 | assert_equal 'GET', request.method 38 | assert_equal '/test', request.normalize_uri.path 39 | assert_equal "key=value", request.normalize_uri.query 40 | assert_equal_authz_headers "OAuth oauth_nonce=\"225579211881198842005988698334675835446\", oauth_signature_method=\"PLAINTEXT\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"5888bf0345e5d237%263196ffd991c8ebdb\", oauth_version=\"1.0\"", authz_header(request) 41 | end 42 | 43 | def test_that_using_auth_headers_on_post_requests_works 44 | request = create_client(:uri => "http://example.com/test", :method => "POST", :body => @request_parameters, :head => {"Content-Type" => "application/x-www-form-urlencoded"}) 45 | request.oauth!(@http, @consumer, @token, {:nonce => @nonce, :timestamp => @timestamp}) 46 | 47 | assert_equal 'POST', request.method 48 | assert_equal '/test', request.uri.path 49 | assert_equal 'key=value', request.normalize_body 50 | assert_equal_authz_headers "OAuth oauth_nonce=\"225579211881198842005988698334675835446\", oauth_signature_method=\"HMAC-SHA1\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"26g7wHTtNO6ZWJaLltcueppHYiI%3D\", oauth_version=\"1.0\"", authz_header(request) 51 | end 52 | 53 | protected 54 | 55 | def create_client(options = {}) 56 | method = options.delete(:method) || "GET" 57 | uri = options.delete(:uri) || @request_uri.to_s 58 | client = EventMachine::HttpClient.new("") 59 | client.uri = URI.parse(uri) 60 | client.method = method.to_s.upcase 61 | client.options = options 62 | client 63 | end 64 | 65 | def authz_header(request) 66 | headers = request.options[:head] || {} 67 | headers['Authorization'].to_s 68 | end 69 | 70 | def assert_equal_authz_headers(expected, actual) 71 | assert !actual.nil? 72 | assert_equal expected[0,6], actual[0, 6] 73 | assert_equal expected[6..1].split(', ').sort, actual[6..1].split(', ').sort 74 | end 75 | 76 | end 77 | 78 | rescue LoadError => e 79 | warn "! problem loading em-http, skipping these tests: #{e}" 80 | end 81 | -------------------------------------------------------------------------------- /test/units/test_oauth_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class TestOAuthHelper < Minitest::Test 4 | 5 | def test_parse_valid_header 6 | header = 'OAuth ' \ 7 | 'realm="http://example.com/method", ' \ 8 | 'oauth_consumer_key="vince_clortho", ' \ 9 | 'oauth_token="token_value", ' \ 10 | 'oauth_signature_method="HMAC-SHA1", ' \ 11 | 'oauth_signature="signature_here", ' \ 12 | 'oauth_timestamp="1240004133", oauth_nonce="nonce", ' \ 13 | 'oauth_version="1.0" ' 14 | 15 | params = OAuth::Helper.parse_header(header) 16 | 17 | assert_equal "http://example.com/method", params['realm'] 18 | assert_equal "vince_clortho", params['oauth_consumer_key'] 19 | assert_equal "token_value", params['oauth_token'] 20 | assert_equal "HMAC-SHA1", params['oauth_signature_method'] 21 | assert_equal "signature_here", params['oauth_signature'] 22 | assert_equal "1240004133", params['oauth_timestamp'] 23 | assert_equal "nonce", params['oauth_nonce'] 24 | assert_equal "1.0", params['oauth_version'] 25 | end 26 | 27 | def test_parse_header_ill_formed 28 | header = "OAuth garbage" 29 | 30 | assert_raises OAuth::Problem do 31 | OAuth::Helper.parse_header(header) 32 | end 33 | end 34 | 35 | def test_parse_header_contains_equals 36 | header = 'OAuth ' \ 37 | 'realm="http://example.com/method", ' \ 38 | 'oauth_consumer_key="vince_clortho", ' \ 39 | 'oauth_token="token_value", ' \ 40 | 'oauth_signature_method="HMAC-SHA1", ' \ 41 | 'oauth_signature="signature_here_with_=", ' \ 42 | 'oauth_timestamp="1240004133", oauth_nonce="nonce", ' \ 43 | 'oauth_version="1.0" ' 44 | 45 | assert_raises OAuth::Problem do 46 | OAuth::Helper.parse_header(header) 47 | end 48 | end 49 | 50 | def test_parse_valid_header_with_and_signs 51 | header = 'OAuth ' \ 52 | 'realm="http://example.com/method"&' \ 53 | 'oauth_consumer_key="vince_clortho"&' \ 54 | 'oauth_token="token_value"&' \ 55 | 'oauth_signature_method="HMAC-SHA1"&' \ 56 | 'oauth_signature="signature_here"&' \ 57 | 'oauth_timestamp="1240004133"&oauth_nonce="nonce"&' \ 58 | 'oauth_version="1.0"' 59 | 60 | params = OAuth::Helper.parse_header(header) 61 | 62 | assert_equal "http://example.com/method", params['realm'] 63 | assert_equal "vince_clortho", params['oauth_consumer_key'] 64 | assert_equal "token_value", params['oauth_token'] 65 | assert_equal "HMAC-SHA1", params['oauth_signature_method'] 66 | assert_equal "signature_here", params['oauth_signature'] 67 | assert_equal "1240004133", params['oauth_timestamp'] 68 | assert_equal "nonce", params['oauth_nonce'] 69 | assert_equal "1.0", params['oauth_version'] 70 | end 71 | 72 | def test_normalize 73 | params = { 74 | 'oauth_nonce' => 'nonce', 75 | 'weight' => { :value => "65" }, 76 | 'oauth_signature_method' => 'HMAC-SHA1', 77 | 'oauth_timestamp' => "1240004133", 78 | 'oauth_consumer_key' => 'vince_clortho', 79 | 'oauth_token' => 'token_value', 80 | 'oauth_version' => "1.0" 81 | } 82 | assert_equal("oauth_consumer_key=vince_clortho&oauth_nonce=nonce&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1240004133&oauth_token=token_value&oauth_version=1.0&weight%5Bvalue%5D=65", OAuth::Helper.normalize(params)) 83 | end 84 | 85 | def test_normalize_nested_query 86 | assert_equal([], OAuth::Helper.normalize_nested_query({})) 87 | assert_equal(["foo=bar"], OAuth::Helper.normalize_nested_query({:foo => 'bar'})) 88 | assert_equal(["prefix%5Bfoo%5D=bar"], OAuth::Helper.normalize_nested_query({:foo => 'bar'}, 'prefix')) 89 | assert_equal(["prefix%5Buser%5D%5Bage%5D=12", 90 | "prefix%5Buser%5D%5Bdate%5D=2011-10-05", 91 | "prefix%5Buser%5D%5Btwitter_id%5D=123"], OAuth::Helper.normalize_nested_query({:user => {:twitter_id => 123, :date => '2011-10-05', :age => 12}}, 'prefix')) 92 | end 93 | 94 | end 95 | -------------------------------------------------------------------------------- /test/units/test_rest_client_request_proxy.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | begin 4 | require 'oauth/request_proxy/rest_client_request' 5 | require 'rest-client' 6 | 7 | class RestlClientRequestProxyTest < Minitest::Test 8 | 9 | def test_that_proxy_simple_get_request_works 10 | request = ::RestClient::Request.new(method: :get, url: "http://example.com/test?key=value") 11 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value'}) 12 | 13 | expected_parameters = {'key' => ['value']} 14 | assert_equal expected_parameters, request_proxy.parameters_for_signature 15 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 16 | assert_equal 'GET', request_proxy.method 17 | end 18 | 19 | def test_that_proxy_simple_post_request_works_with_arguments 20 | request = ::RestClient::Request.new(method: :post, url: "http://example.com/test") 21 | params = {'key' => 'value'} 22 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test', :parameters => params}) 23 | 24 | expected_parameters = {'key' => 'value'} 25 | assert_equal expected_parameters, request_proxy.parameters_for_signature 26 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 27 | assert_equal 'POST', request_proxy.method 28 | end 29 | 30 | def test_that_proxy_simple_post_request_works_with_form_data 31 | request = ::RestClient::Request.new(method: :post, url: "http://example.com/test", 32 | payload: {'key' => 'value'}, 33 | headers: {'Content-Type' => 'application/x-www-form-urlencoded'}) 34 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test'}) 35 | 36 | expected_parameters = {'key' => 'value'} 37 | assert_equal expected_parameters, request_proxy.parameters_for_signature 38 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 39 | assert_equal 'POST', request_proxy.method 40 | end 41 | 42 | def test_that_proxy_simple_put_request_works_with_arguments 43 | request = ::RestClient::Request.new(method: :put, url: "http://example.com/test") 44 | params = {'key' => 'value'} 45 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test', :parameters => params}) 46 | 47 | expected_parameters = {'key' => 'value'} 48 | assert_equal expected_parameters, request_proxy.parameters_for_signature 49 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 50 | assert_equal 'PUT', request_proxy.method 51 | end 52 | 53 | def test_that_proxy_simple_put_request_works_with_form_data 54 | request = ::RestClient::Request.new(method: :put, url: "http://example.com/test", 55 | payload: {'key' => 'value'}, 56 | headers: {'Content-Type' => 'application/x-www-form-urlencoded'}) 57 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test'}) 58 | 59 | expected_parameters = {'key' => 'value'} 60 | assert_equal expected_parameters, request_proxy.parameters_for_signature 61 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 62 | assert_equal 'PUT', request_proxy.method 63 | end 64 | 65 | def test_that_proxy_post_request_works_with_mixed_parameter_sources 66 | request = ::RestClient::Request.new(url: 'http://example.com/test?key=value', 67 | method: :post, 68 | payload: {'key2' => 'value2'}, 69 | headers: {'Content-Type' => 'application/x-www-form-urlencoded'}) 70 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value', :parameters => {'key3' => 'value3'}}) 71 | 72 | expected_parameters = {'key' => ['value'], 'key2' => 'value2', 'key3' => 'value3'} 73 | assert_equal expected_parameters, request_proxy.parameters_for_signature 74 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 75 | assert_equal 'POST', request_proxy.method 76 | end 77 | 78 | end 79 | rescue LoadError => e 80 | warn "! problem loading rest-client, skipping these tests: #{e}" 81 | end 82 | -------------------------------------------------------------------------------- /test/units/test_rsa_sha1.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'oauth/consumer' 4 | require 'oauth/signature/rsa/sha1' 5 | 6 | class TestSignatureRsaSha1 < Minitest::Test 7 | 8 | def setup 9 | @request = Net::HTTP::Get.new("/photos?file=vacaction.jpg&size=original&oauth_version=1.0&oauth_consumer_key=#{consumer_key}&oauth_timestamp=1196666512&oauth_nonce=13917289812797014437&oauth_signature_method=RSA-SHA1") 10 | 11 | @consumer = OAuth::Consumer.new(consumer_key, pkey_rsa) 12 | end 13 | 14 | def test_that_rsa_sha1_implements_rsa_sha1 15 | assert OAuth::Signature.available_methods.include?('rsa-sha1') 16 | end 17 | 18 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature_base_string 19 | sbs = OAuth::Signature.signature_base_string(@request, { :consumer => @consumer, 20 | :uri => 'http://photos.example.net/photos' } ) 21 | 22 | assert_equal 'GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacaction.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3D13917289812797014437%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1196666512%26oauth_version%3D1.0%26size%3Doriginal', sbs 23 | end 24 | 25 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature 26 | signature = OAuth::Signature.sign(@request, { :consumer => @consumer, 27 | :uri => 'http://photos.example.net/photos' } ) 28 | 29 | assert_equal 'jvTp/wX1TYtByB1m+Pbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2/9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW//e+RinhejgCuzoH26dyF8iY2ZZ/5D1ilgeijhV/vBka5twt399mXwaYdCwFYE=', signature 30 | end 31 | 32 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature_using_private_key_file 33 | @consumer = OAuth::Consumer.new(consumer_key,nil) 34 | 35 | signature = OAuth::Signature.sign(@request, { :consumer => @consumer, 36 | :private_key_file=>pem_path, 37 | :uri => 'http://photos.example.net/photos' } ) 38 | 39 | assert_equal 'jvTp/wX1TYtByB1m+Pbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2/9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW//e+RinhejgCuzoH26dyF8iY2ZZ/5D1ilgeijhV/vBka5twt399mXwaYdCwFYE=', signature 40 | end 41 | 42 | def test_that_get_request_from_oauth_test_cases_verifies_signature 43 | @request = Net::HTTP::Get.new("/photos?oauth_signature_method=RSA-SHA1&oauth_version=1.0&oauth_consumer_key=#{consumer_key}&oauth_timestamp=1196666512&oauth_nonce=13917289812797014437&file=vacaction.jpg&size=original&oauth_signature=jvTp%2FwX1TYtByB1m%2BPbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2%2F9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW%2F%2Fe%2BRinhejgCuzoH26dyF8iY2ZZ%2F5D1ilgeijhV%2FvBka5twt399mXwaYdCwFYE%3D") 44 | @consumer = OAuth::Consumer.new(consumer_key, x509_certificate) 45 | 46 | assert OAuth::Signature.verify(@request, { :consumer => @consumer, 47 | :uri => 'http://photos.example.net/photos' } ) 48 | 49 | end 50 | 51 | def test_that_get_request_from_oauth_test_cases_verifies_signature_with_pem 52 | @request = Net::HTTP::Get.new("/photos?oauth_signature_method=RSA-SHA1&oauth_version=1.0&oauth_consumer_key=#{consumer_key}&oauth_timestamp=1196666512&oauth_nonce=13917289812797014437&file=vacaction.jpg&size=original&oauth_signature=jvTp%2FwX1TYtByB1m%2BPbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2%2F9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW%2F%2Fe%2BRinhejgCuzoH26dyF8iY2ZZ%2F5D1ilgeijhV%2FvBka5twt399mXwaYdCwFYE%3D") 53 | assert OAuth::Signature.verify(@request, { :consumer => @consumer, 54 | :uri => 'http://photos.example.net/photos' } ) 55 | end 56 | 57 | private 58 | 59 | def consumer_key 60 | 'dpf43f3p2l4k3l03' 61 | end 62 | 63 | def x509_certificate 64 | OpenSSL::X509::Certificate.new(IO.read(cert_path)) 65 | end 66 | 67 | def pkey_rsa 68 | OpenSSL::PKey::RSA.new(IO.read(pem_path)) 69 | end 70 | 71 | def cert_path 72 | File.dirname(__FILE__) + "/../keys/rsa.cert" 73 | end 74 | 75 | def pem_path 76 | File.dirname(__FILE__) + "/../keys/rsa.pem" 77 | end 78 | 79 | end 80 | -------------------------------------------------------------------------------- /lib/oauth/helper.rb: -------------------------------------------------------------------------------- 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 | _escape(value.to_s.to_str) 13 | rescue ArgumentError 14 | _escape(value.to_s.to_str.force_encoding(Encoding::UTF_8)) 15 | end 16 | 17 | def _escape(string) 18 | URI::DEFAULT_PARSER.escape(string, OAuth::RESERVED_CHARACTERS) 19 | end 20 | 21 | def unescape(value) 22 | URI::DEFAULT_PARSER.unescape(value.gsub('+', '%2B')) 23 | end 24 | 25 | # Generate a random key of up to +size+ bytes. The value returned is Base64 encoded with non-word 26 | # characters removed. 27 | def generate_key(size=32) 28 | Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/, '') 29 | end 30 | 31 | alias_method :generate_nonce, :generate_key 32 | 33 | def generate_timestamp #:nodoc: 34 | Time.now.to_i.to_s 35 | end 36 | 37 | # Normalize a +Hash+ of parameter values. Parameters are sorted by name, using lexicographical 38 | # byte value ordering. If two or more parameters share the same name, they are sorted by their value. 39 | # Parameters are concatenated in their sorted order into a single string. For each parameter, the name 40 | # is separated from the corresponding value by an "=" character, even if the value is empty. Each 41 | # name-value pair is separated by an "&" character. 42 | # 43 | # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1] 44 | def normalize(params) 45 | params.sort.map do |k, values| 46 | if values.is_a?(Array) 47 | # make sure the array has an element so we don't lose the key 48 | values << nil if values.empty? 49 | # multiple values were provided for a single key 50 | values.sort.collect do |v| 51 | [escape(k),escape(v)] * "=" 52 | end 53 | elsif values.is_a?(Hash) 54 | normalize_nested_query(values, k) 55 | else 56 | [escape(k),escape(values)] * "=" 57 | end 58 | end * "&" 59 | end 60 | 61 | #Returns a string representation of the Hash like in URL query string 62 | # build_nested_query({:level_1 => {:level_2 => ['value_1','value_2']}}, 'prefix')) 63 | # #=> ["prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_1", "prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_2"] 64 | def normalize_nested_query(value, prefix = nil) 65 | case value 66 | when Array 67 | value.map do |v| 68 | normalize_nested_query(v, "#{prefix}[]") 69 | end.flatten.sort 70 | when Hash 71 | value.map do |k, v| 72 | normalize_nested_query(v, prefix ? "#{prefix}[#{k}]" : k) 73 | end.flatten.sort 74 | else 75 | [escape(prefix), escape(value)] * "=" 76 | end 77 | end 78 | 79 | # Parse an Authorization / WWW-Authenticate header into a hash. Takes care of unescaping and 80 | # removing surrounding quotes. Raises a OAuth::Problem if the header is not parsable into a 81 | # valid hash. Does not validate the keys or values. 82 | # 83 | # hash = parse_header(headers['Authorization'] || headers['WWW-Authenticate']) 84 | # hash['oauth_timestamp'] 85 | # #=>"1234567890" 86 | # 87 | def parse_header(header) 88 | # decompose 89 | params = header[6,header.length].split(/[,=&]/) 90 | 91 | # odd number of arguments - must be a malformed header. 92 | raise OAuth::Problem.new("Invalid authorization header") if params.size % 2 != 0 93 | 94 | params.map! do |v| 95 | # strip and unescape 96 | val = unescape(v.strip) 97 | # strip quotes 98 | val.sub(/^\"(.*)\"$/, '\1') 99 | end 100 | 101 | # convert into a Hash 102 | Hash[*params.flatten] 103 | end 104 | 105 | def stringify_keys(hash) 106 | new_h = {} 107 | hash.each do |k, v| 108 | new_h[k.to_s] = v.is_a?(Hash) ? stringify_keys(v) : v 109 | end 110 | new_h 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /test/units/test_typhoeus_request_proxy.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | begin 4 | 5 | require 'oauth/request_proxy/typhoeus_request' 6 | require 'typhoeus' 7 | 8 | class TyphoeusRequestProxyTest < Minitest::Test 9 | 10 | def test_that_proxy_simple_get_request_works 11 | request = ::Typhoeus::Request.new('/test?key=value') 12 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value'}) 13 | 14 | expected_parameters = {'key' => ['value']} 15 | assert_equal expected_parameters, request_proxy.parameters_for_signature 16 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 17 | assert_equal 'GET', request_proxy.method 18 | end 19 | 20 | def test_that_proxy_simple_post_request_works_with_arguments 21 | request = Typhoeus::Request.new('/test', :method => :post) 22 | params = {'key' => 'value'} 23 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test', :parameters => params}) 24 | 25 | expected_parameters = {'key' => 'value'} 26 | assert_equal expected_parameters, request_proxy.parameters_for_signature 27 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 28 | assert_equal 'POST', request_proxy.method 29 | end 30 | 31 | def test_that_proxy_simple_post_request_works_with_form_data 32 | request = Typhoeus::Request.new('/test', :method => :post, 33 | :params => {'key' => 'value'}, 34 | :headers => {'Content-Type' => 'application/x-www-form-urlencoded'}) 35 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test'}) 36 | 37 | expected_parameters = {'key' => ['value']} 38 | assert_equal expected_parameters, request_proxy.parameters_for_signature 39 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 40 | assert_equal 'POST', request_proxy.method 41 | end 42 | 43 | def test_that_proxy_simple_put_request_works_with_arguments 44 | request = Typhoeus::Request.new('/test', :method => :put) 45 | params = {'key' => 'value'} 46 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test', :parameters => params}) 47 | 48 | expected_parameters = {'key' => 'value'} 49 | assert_equal expected_parameters, request_proxy.parameters_for_signature 50 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 51 | assert_equal 'PUT', request_proxy.method 52 | end 53 | 54 | def test_that_proxy_simple_put_request_works_with_form_data 55 | request = Typhoeus::Request.new('/test', :method => :put, :params => {'key' => 'value'}) 56 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test'}) 57 | 58 | expected_parameters = {'key' => ['value']} 59 | assert_equal expected_parameters, request_proxy.parameters_for_signature 60 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 61 | assert_equal 'PUT', request_proxy.method 62 | end 63 | 64 | def test_that_proxy_simple_patch_request_works_with_arguments 65 | request = Typhoeus::Request.new('/test', :method => :patch) 66 | params = {'key' => 'value'} 67 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test', :parameters => params}) 68 | 69 | expected_parameters = {'key' => 'value'} 70 | assert_equal expected_parameters, request_proxy.parameters_for_signature 71 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 72 | assert_equal 'PATCH', request_proxy.method 73 | end 74 | 75 | def test_that_proxy_simple_patch_request_works_with_form_data 76 | request = Typhoeus::Request.new('/test', :method => :patch, :params => {'key' => 'value'}) 77 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test'}) 78 | 79 | expected_parameters = {'key' => ['value']} 80 | assert_equal expected_parameters, request_proxy.parameters_for_signature 81 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 82 | assert_equal 'PATCH', request_proxy.method 83 | end 84 | 85 | def test_that_proxy_post_request_works_with_mixed_parameter_sources 86 | request = Typhoeus::Request.new('/test?key=value', 87 | :method => :post, 88 | :params => {'key2' => 'value2'}, 89 | :headers => {'Content-Type' => 'application/x-www-form-urlencoded'}) 90 | request_proxy = OAuth::RequestProxy.proxy(request, {:uri => 'http://example.com/test?key=value', :parameters => {'key3' => 'value3'}}) 91 | 92 | expected_parameters = {'key' => ['value'], 'key2' => ['value2'], 'key3' => 'value3'} 93 | assert_equal expected_parameters, request_proxy.parameters_for_signature 94 | assert_equal 'http://example.com/test', request_proxy.normalized_uri 95 | assert_equal 'POST', request_proxy.method 96 | end 97 | end 98 | 99 | rescue LoadError => e 100 | warn "! problem loading typhoeus, skipping these tests: #{e}" 101 | end 102 | -------------------------------------------------------------------------------- /lib/oauth/client/em_http.rb: -------------------------------------------------------------------------------- 1 | require 'em-http' 2 | require 'oauth/helper' 3 | require 'oauth/request_proxy/em_http_request' 4 | 5 | # Extensions for em-http so that we can use consumer.sign! with an EventMachine::HttpClient 6 | # instance. This is purely syntactic sugar. 7 | class EventMachine::HttpClient 8 | 9 | attr_reader :oauth_helper 10 | 11 | # Add the OAuth information to an HTTP request. Depending on the options[:scheme] setting 12 | # this may add a header, additional query string parameters, or additional POST body parameters. 13 | # The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+ 14 | # header. 15 | # 16 | # * http - Configured Net::HTTP instance, ignored in this scenario except for getting host. 17 | # * consumer - OAuth::Consumer instance 18 | # * token - OAuth::Token instance 19 | # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+, 20 | # +signature_method+, +nonce+, +timestamp+) 21 | # 22 | # This method also modifies the User-Agent header to add the OAuth gem version. 23 | # 24 | # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1] 25 | def oauth!(http, consumer = nil, token = nil, options = {}) 26 | options = { :request_uri => normalized_oauth_uri(http), 27 | :consumer => consumer, 28 | :token => token, 29 | :scheme => 'header', 30 | :signature_method => nil, 31 | :nonce => nil, 32 | :timestamp => nil }.merge(options) 33 | 34 | @oauth_helper = OAuth::Client::Helper.new(self, options) 35 | self.__send__(:"set_oauth_#{options[:scheme]}") 36 | end 37 | 38 | # Create a string suitable for signing for an HTTP request. This process involves parameter 39 | # normalization as specified in the OAuth specification. The exact normalization also depends 40 | # on the options[:scheme] being used so this must match what will be used for the request 41 | # itself. The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+ 42 | # header. 43 | # 44 | # * http - Configured Net::HTTP instance 45 | # * consumer - OAuth::Consumer instance 46 | # * token - OAuth::Token instance 47 | # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+, 48 | # +signature_method+, +nonce+, +timestamp+) 49 | # 50 | # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1] 51 | def signature_base_string(http, consumer = nil, token = nil, options = {}) 52 | options = { :request_uri => normalized_oauth_uri(http), 53 | :consumer => consumer, 54 | :token => token, 55 | :scheme => 'header', 56 | :signature_method => nil, 57 | :nonce => nil, 58 | :timestamp => nil }.merge(options) 59 | 60 | OAuth::Client::Helper.new(self, options).signature_base_string 61 | end 62 | 63 | # This code was lifted from the em-http-request because it was removed from 64 | # the gem June 19, 2010 65 | # see: http://github.com/igrigorik/em-http-request/commit/d536fc17d56dbe55c487eab01e2ff9382a62598b 66 | def normalize_uri 67 | @normalized_uri ||= begin 68 | uri = @uri.dup 69 | encoded_query = encode_query(@uri, @options[:query]) 70 | path, query = encoded_query.split("?", 2) 71 | uri.query = query unless encoded_query.empty? 72 | uri.path = path 73 | uri 74 | end 75 | end 76 | 77 | protected 78 | 79 | def combine_query(path, query, uri_query) 80 | combined_query = if query.kind_of?(Hash) 81 | query.map { |k, v| encode_param(k, v) }.join('&') 82 | else 83 | query.to_s 84 | end 85 | if !uri_query.to_s.empty? 86 | combined_query = [combined_query, uri_query].reject {|part| part.empty?}.join("&") 87 | end 88 | combined_query.to_s.empty? ? path : "#{path}?#{combined_query}" 89 | end 90 | 91 | # Since we expect to get the host etc details from the http instance (...), 92 | # we create a fake url here. Surely this is a horrible, horrible idea? 93 | def normalized_oauth_uri(http) 94 | uri = URI.parse(normalize_uri.path) 95 | uri.host = http.address 96 | uri.port = http.port 97 | 98 | if http.respond_to?(:use_ssl?) && http.use_ssl? 99 | uri.scheme = "https" 100 | else 101 | uri.scheme = "http" 102 | end 103 | uri.to_s 104 | end 105 | 106 | def set_oauth_header 107 | headers = (self.options[:head] ||= {}) 108 | headers['Authorization'] = @oauth_helper.header 109 | end 110 | 111 | def set_oauth_body 112 | raise NotImplementedError, 'please use the set_oauth_header method instead' 113 | end 114 | 115 | def set_oauth_query_string 116 | raise NotImplementedError, 'please use the set_oauth_header method instead' 117 | end 118 | 119 | end 120 | -------------------------------------------------------------------------------- /test/units/test_client_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'oauth/client' 4 | 5 | class ClientHelperTest < Minitest::Test 6 | 7 | def setup 8 | @consumer=OAuth::Consumer.new( 9 | 'consumer_key_86cad9', '5888bf0345e5d237', 10 | { 11 | :site=>"http://blabla.bla", 12 | :proxy=>"http://user:password@proxy.bla:8080", 13 | :request_token_path=>"/oauth/example/request_token.php", 14 | :access_token_path=>"/oauth/example/access_token.php", 15 | :authorize_path=>"/oauth/example/authorize.php", 16 | :scheme=>:header, 17 | :http_method=>:get 18 | }) 19 | end 20 | 21 | def test_oauth_parameters_allow_empty_params_default 22 | helper = OAuth::Client::Helper.new(nil, { 23 | :consumer => @consumer 24 | }) 25 | helper.stub :timestamp, '0' do 26 | helper.stub :nonce, 'nonce' do 27 | expected = { 28 | "oauth_consumer_key"=>"consumer_key_86cad9", 29 | "oauth_signature_method"=>"HMAC-SHA1", 30 | "oauth_timestamp"=>"0", 31 | "oauth_nonce"=>"nonce", 32 | "oauth_version"=>"1.0" 33 | } 34 | assert_equal expected, helper.oauth_parameters 35 | end 36 | end 37 | end 38 | 39 | def test_oauth_parameters_allow_empty_params_true 40 | input = true 41 | helper = OAuth::Client::Helper.new(nil, { 42 | :consumer => @consumer, 43 | :allow_empty_params => input 44 | }) 45 | helper.stub :timestamp, '0' do 46 | helper.stub :nonce, 'nonce' do 47 | expected = { 48 | "oauth_body_hash"=>nil, 49 | "oauth_callback"=>nil, 50 | "oauth_consumer_key"=>"consumer_key_86cad9", 51 | "oauth_token"=>"", 52 | "oauth_signature_method"=>"HMAC-SHA1", 53 | "oauth_timestamp"=>"0", 54 | "oauth_nonce"=>"nonce", 55 | "oauth_verifier"=>nil, 56 | "oauth_version"=>"1.0", 57 | "oauth_session_handle"=>nil 58 | } 59 | assert_equal expected, helper.oauth_parameters 60 | end 61 | end 62 | end 63 | 64 | def test_oauth_parameters_allow_empty_params_false 65 | input = false 66 | helper = OAuth::Client::Helper.new(nil, { 67 | :consumer => @consumer, 68 | :allow_empty_params => input 69 | }) 70 | helper.stub :timestamp, '0' do 71 | helper.stub :nonce, 'nonce' do 72 | expected = { 73 | "oauth_consumer_key"=>"consumer_key_86cad9", 74 | "oauth_signature_method"=>"HMAC-SHA1", 75 | "oauth_timestamp"=>"0", 76 | "oauth_nonce"=>"nonce", 77 | "oauth_version"=>"1.0" 78 | } 79 | assert_equal expected, helper.oauth_parameters 80 | end 81 | end 82 | end 83 | 84 | def test_oauth_parameters_allow_empty_params_only_oauth_token_as_string 85 | input = 'oauth_token' 86 | helper = OAuth::Client::Helper.new(nil, { 87 | :consumer => @consumer, 88 | :allow_empty_params => input 89 | }) 90 | helper.stub :timestamp, '0' do 91 | helper.stub :nonce, 'nonce' do 92 | expected = { 93 | "oauth_consumer_key"=>"consumer_key_86cad9", 94 | "oauth_token"=>"", 95 | "oauth_signature_method"=>"HMAC-SHA1", 96 | "oauth_timestamp"=>"0", 97 | "oauth_nonce"=>"nonce", 98 | "oauth_version"=>"1.0", 99 | } 100 | assert_equal expected, helper.oauth_parameters 101 | end 102 | end 103 | end 104 | 105 | def test_oauth_parameters_allow_empty_params_only_oauth_token_as_array 106 | input = ['oauth_token'] 107 | helper = OAuth::Client::Helper.new(nil, { 108 | :consumer => @consumer, 109 | :allow_empty_params => input 110 | }) 111 | helper.stub :timestamp, '0' do 112 | helper.stub :nonce, 'nonce' do 113 | expected = { 114 | "oauth_consumer_key"=>"consumer_key_86cad9", 115 | "oauth_token"=>"", 116 | "oauth_signature_method"=>"HMAC-SHA1", 117 | "oauth_timestamp"=>"0", 118 | "oauth_nonce"=>"nonce", 119 | "oauth_version"=>"1.0", 120 | } 121 | assert_equal expected, helper.oauth_parameters 122 | end 123 | end 124 | end 125 | 126 | def test_oauth_parameters_allow_empty_params_oauth_token_and_oauth_session_handle 127 | input = ['oauth_token', 'oauth_session_handle'] 128 | helper = OAuth::Client::Helper.new(nil, { 129 | :consumer => @consumer, 130 | :allow_empty_params => input 131 | }) 132 | helper.stub :timestamp, '0' do 133 | helper.stub :nonce, 'nonce' do 134 | expected = { 135 | "oauth_consumer_key"=>"consumer_key_86cad9", 136 | "oauth_token"=>"", 137 | "oauth_signature_method"=>"HMAC-SHA1", 138 | "oauth_timestamp"=>"0", 139 | "oauth_nonce"=>"nonce", 140 | "oauth_version"=>"1.0", 141 | "oauth_session_handle"=>nil 142 | } 143 | assert_equal expected, helper.oauth_parameters 144 | end 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/base.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/request_proxy' 2 | require 'oauth/helper' 3 | 4 | module OAuth::RequestProxy 5 | class Base 6 | include OAuth::Helper 7 | 8 | def self.proxies(klass) 9 | OAuth::RequestProxy.available_proxies[klass] = self 10 | end 11 | 12 | attr_accessor :request, :options, :unsigned_parameters 13 | 14 | def initialize(request, options = {}) 15 | @request = request 16 | @unsigned_parameters = (options[:unsigned_parameters] || []).map {|param| param.to_s} 17 | @options = options 18 | end 19 | 20 | ## OAuth parameters 21 | 22 | def oauth_callback 23 | parameters['oauth_callback'] 24 | end 25 | 26 | def oauth_consumer_key 27 | parameters['oauth_consumer_key'] 28 | end 29 | 30 | def oauth_nonce 31 | parameters['oauth_nonce'] 32 | end 33 | 34 | def oauth_signature 35 | # TODO can this be nil? 36 | [parameters['oauth_signature']].flatten.first || "" 37 | end 38 | 39 | def oauth_signature_method 40 | case parameters['oauth_signature_method'] 41 | when Array 42 | parameters['oauth_signature_method'].first 43 | else 44 | parameters['oauth_signature_method'] 45 | end 46 | end 47 | 48 | def oauth_timestamp 49 | parameters['oauth_timestamp'] 50 | end 51 | 52 | def oauth_token 53 | parameters['oauth_token'] 54 | end 55 | 56 | def oauth_verifier 57 | parameters['oauth_verifier'] 58 | end 59 | 60 | def oauth_version 61 | parameters["oauth_version"] 62 | end 63 | 64 | # TODO deprecate these 65 | alias_method :consumer_key, :oauth_consumer_key 66 | alias_method :token, :oauth_token 67 | alias_method :nonce, :oauth_nonce 68 | alias_method :timestamp, :oauth_timestamp 69 | alias_method :signature, :oauth_signature 70 | alias_method :signature_method, :oauth_signature_method 71 | 72 | ## Parameter accessors 73 | 74 | def parameters 75 | raise NotImplementedError, "Must be implemented by subclasses" 76 | end 77 | 78 | def parameters_for_signature 79 | parameters.select { |k,v| not signature_and_unsigned_parameters.include?(k) } 80 | end 81 | 82 | def oauth_parameters 83 | parameters.select { |k,v| OAuth::PARAMETERS.include?(k) }.reject { |k,v| v == "" } 84 | end 85 | 86 | def non_oauth_parameters 87 | parameters.reject { |k,v| OAuth::PARAMETERS.include?(k) } 88 | end 89 | 90 | def signature_and_unsigned_parameters 91 | unsigned_parameters+["oauth_signature"] 92 | end 93 | 94 | # See 9.1.2 in specs 95 | def normalized_uri 96 | u = URI.parse(uri) 97 | "#{u.scheme.downcase}://#{u.host.downcase}#{(u.scheme.downcase == 'http' && u.port != 80) || (u.scheme.downcase == 'https' && u.port != 443) ? ":#{u.port}" : ""}#{(u.path && u.path != '') ? u.path : '/'}" 98 | end 99 | 100 | # See 9.1.1. in specs Normalize Request Parameters 101 | def normalized_parameters 102 | normalize(parameters_for_signature) 103 | end 104 | 105 | def sign(options = {}) 106 | OAuth::Signature.sign(self, options) 107 | end 108 | 109 | def sign!(options = {}) 110 | parameters["oauth_signature"] = sign(options) 111 | @signed = true 112 | signature 113 | end 114 | 115 | # See 9.1 in specs 116 | def signature_base_string 117 | base = [method, normalized_uri, normalized_parameters] 118 | base.map { |v| escape(v) }.join("&") 119 | end 120 | 121 | # Has this request been signed yet? 122 | def signed? 123 | @signed 124 | end 125 | 126 | # URI, including OAuth parameters 127 | def signed_uri(with_oauth = true) 128 | if signed? 129 | if with_oauth 130 | params = parameters 131 | else 132 | params = non_oauth_parameters 133 | end 134 | 135 | [uri, normalize(params)] * "?" 136 | else 137 | STDERR.puts "This request has not yet been signed!" 138 | end 139 | end 140 | 141 | # Authorization header for OAuth 142 | def oauth_header(options = {}) 143 | header_params_str = oauth_parameters.map { |k,v| "#{k}=\"#{escape(v)}\"" }.join(', ') 144 | 145 | realm = "realm=\"#{options[:realm]}\", " if options[:realm] 146 | "OAuth #{realm}#{header_params_str}" 147 | end 148 | 149 | def query_string_blank? 150 | if uri = request.env['REQUEST_URI'] 151 | uri.split('?', 2)[1].nil? 152 | else 153 | request.query_string.match(/\A\s*\z/) 154 | end 155 | end 156 | 157 | protected 158 | 159 | def header_params 160 | %w( X-HTTP_AUTHORIZATION Authorization HTTP_AUTHORIZATION ).each do |header| 161 | next unless request.env.include?(header) 162 | 163 | header = request.env[header] 164 | next unless header[0,6] == 'OAuth ' 165 | 166 | # parse the header into a Hash 167 | oauth_params = OAuth::Helper.parse_header(header) 168 | 169 | # remove non-OAuth parameters 170 | oauth_params.reject! { |k,v| k !~ /^oauth_/ } 171 | 172 | return oauth_params 173 | end 174 | 175 | return {} 176 | end 177 | end 178 | end 179 | -------------------------------------------------------------------------------- /lib/oauth/client/net_http.rb: -------------------------------------------------------------------------------- 1 | require 'oauth/helper' 2 | require 'oauth/request_proxy/net_http' 3 | 4 | class Net::HTTPGenericRequest 5 | include OAuth::Helper 6 | 7 | attr_reader :oauth_helper 8 | 9 | # Add the OAuth information to an HTTP request. Depending on the options[:scheme] setting 10 | # this may add a header, additional query string parameters, or additional POST body parameters. 11 | # The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+ 12 | # header. 13 | # 14 | # * http - Configured Net::HTTP instance 15 | # * consumer - OAuth::Consumer instance 16 | # * token - OAuth::Token instance 17 | # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+, 18 | # +signature_method+, +nonce+, +timestamp+) 19 | # 20 | # This method also modifies the User-Agent header to add the OAuth gem version. 21 | # 22 | # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1], 23 | # {OAuth Request Body Hash 1.0 Draft 4}[http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/4/spec.html, 24 | # http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html#when_to_include] 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 options[:scheme] 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 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.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 | # http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html#when_to_include] 48 | def signature_base_string(http, consumer = nil, token = nil, options = {}) 49 | helper_options = oauth_helper_options(http, consumer, token, options) 50 | @oauth_helper = OAuth::Client::Helper.new(self, helper_options) 51 | @oauth_helper.hash_body if oauth_body_hash_required? 52 | @oauth_helper.signature_base_string 53 | end 54 | 55 | private 56 | 57 | def oauth_helper_options(http, consumer, token, options) 58 | { :request_uri => oauth_full_request_uri(http,options), 59 | :consumer => consumer, 60 | :token => token, 61 | :scheme => 'header', 62 | :signature_method => nil, 63 | :nonce => nil, 64 | :timestamp => nil }.merge(options) 65 | end 66 | 67 | def oauth_full_request_uri(http,options) 68 | uri = URI.parse(self.path) 69 | uri.host = http.address 70 | uri.port = http.port 71 | 72 | if options[:request_endpoint] && options[:site] 73 | is_https = options[:site].match(%r(^https://)) 74 | uri.host = options[:site].gsub(%r(^https?://), '') 75 | uri.port ||= is_https ? 443 : 80 76 | end 77 | 78 | if http.respond_to?(:use_ssl?) && http.use_ssl? 79 | uri.scheme = "https" 80 | else 81 | uri.scheme = "http" 82 | end 83 | 84 | uri.to_s 85 | end 86 | 87 | def oauth_body_hash_required? 88 | !@oauth_helper.token_request? && request_body_permitted? && !content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded") 89 | end 90 | 91 | def set_oauth_header 92 | self['Authorization'] = @oauth_helper.header 93 | end 94 | 95 | # FIXME: if you're using a POST body and query string parameters, this method 96 | # will move query string parameters into the body unexpectedly. This may 97 | # cause problems with non-x-www-form-urlencoded bodies submitted to URLs 98 | # containing query string params. If duplicate parameters are present in both 99 | # places, all instances should be included when calculating the signature 100 | # base string. 101 | 102 | def set_oauth_body 103 | self.set_form_data(@oauth_helper.stringify_keys(@oauth_helper.parameters_with_oauth)) 104 | params_with_sig = @oauth_helper.parameters.merge(:oauth_signature => @oauth_helper.signature) 105 | self.set_form_data(@oauth_helper.stringify_keys(params_with_sig)) 106 | end 107 | 108 | def set_oauth_query_string 109 | oauth_params_str = @oauth_helper.oauth_parameters.map { |k,v| [escape(k), escape(v)] * "=" }.join("&") 110 | uri = URI.parse(path) 111 | if uri.query.to_s == "" 112 | uri.query = oauth_params_str 113 | else 114 | uri.query = uri.query + "&" + oauth_params_str 115 | end 116 | 117 | @path = uri.to_s 118 | 119 | @path << "&oauth_signature=#{escape(oauth_helper.signature)}" 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /test/units/test_action_controller_request_proxy.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'oauth/request_proxy/action_controller_request' 4 | 5 | class ActionControllerRequestProxyTest < Minitest::Test 6 | 7 | def request_proxy(request_method = :get, uri_params = {}, body_params = {}) 8 | request = ActionDispatch::TestRequest.create 9 | request.request_uri = '/' 10 | 11 | case request_method 12 | when :post 13 | request.env['REQUEST_METHOD'] = 'POST' 14 | when :put 15 | request.env['REQUEST_METHOD'] = 'PUT' 16 | when :patch 17 | request.env['REQUEST_METHOD'] = 'PATCH' 18 | end 19 | 20 | request.env['REQUEST_URI'] = '/' 21 | request.env['RAW_POST_DATA'] = body_params.to_query 22 | request.env['QUERY_STRING'] = body_params.to_query 23 | request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded' 24 | 25 | yield request if block_given? 26 | OAuth::RequestProxy::ActionControllerRequest.new(request, :parameters => uri_params) 27 | end 28 | 29 | def test_that_proxy_simple_get_request_works_with_query_params 30 | request_proxy = request_proxy(:get, {'key'=>'value'}) 31 | 32 | expected_parameters = [["key", "value"]] 33 | assert_equal expected_parameters, request_proxy.parameters_for_signature 34 | assert_equal 'GET', request_proxy.method 35 | end 36 | 37 | def test_that_proxy_simple_post_request_works_with_query_params 38 | request_proxy = request_proxy(:post, {'key'=>'value'}) 39 | 40 | expected_parameters = [["key", "value"]] 41 | assert_equal expected_parameters, request_proxy.parameters_for_signature 42 | assert_equal 'POST', request_proxy.method 43 | end 44 | 45 | def test_that_proxy_simple_put_request_works_with_query_params 46 | request_proxy = request_proxy(:put, {'key'=>'value'}) 47 | 48 | expected_parameters = [["key", "value"]] 49 | assert_equal expected_parameters, request_proxy.parameters_for_signature 50 | assert_equal 'PUT', request_proxy.method 51 | end 52 | 53 | def test_that_proxy_simple_patch_request_works_with_query_params 54 | request_proxy = request_proxy(:patch, {'key'=>'value'}) 55 | 56 | expected_parameters = [["key", "value"]] 57 | assert_equal expected_parameters, request_proxy.parameters_for_signature 58 | assert_equal 'PATCH', request_proxy.method 59 | end 60 | 61 | def test_that_proxy_simple_get_request_works_with_post_params 62 | request_proxy = request_proxy(:get, {}, {'key'=>'value'}) 63 | 64 | expected_parameters = [] 65 | assert_equal expected_parameters, request_proxy.parameters_for_signature 66 | assert_equal 'GET', request_proxy.method 67 | end 68 | 69 | def test_that_proxy_simple_post_request_works_with_post_params 70 | request_proxy = request_proxy(:post, {}, {'key'=>'value'}) 71 | 72 | expected_parameters = [["key", "value"]] 73 | assert_equal expected_parameters, request_proxy.parameters_for_signature 74 | assert_equal 'POST', request_proxy.method 75 | end 76 | 77 | def test_that_proxy_simple_put_request_works_with_post_params 78 | request_proxy = request_proxy(:put, {}, {'key'=>'value'}) 79 | 80 | expected_parameters = [] 81 | assert_equal expected_parameters, request_proxy.parameters_for_signature 82 | assert_equal 'PUT', request_proxy.method 83 | end 84 | 85 | def test_that_proxy_simple_patch_request_works_with_post_params 86 | request_proxy = request_proxy(:patch, {}, {'key'=>'value'}) 87 | 88 | expected_parameters = [] 89 | assert_equal expected_parameters, request_proxy.parameters_for_signature 90 | assert_equal 'PATCH', request_proxy.method 91 | end 92 | 93 | def test_that_proxy_simple_get_request_works_with_mixed_params 94 | request_proxy = request_proxy(:get, {'key'=>'value'}, {'key2'=>'value2'}) 95 | 96 | expected_parameters = [["key", "value"]] 97 | assert_equal expected_parameters, request_proxy.parameters_for_signature 98 | assert_equal 'GET', request_proxy.method 99 | end 100 | 101 | def test_that_proxy_simple_post_request_works_with_mixed_params 102 | request_proxy = request_proxy(:post, {'key'=>'value'}, {'key2'=>'value2'}) 103 | 104 | expected_parameters = [["key", "value"],["key2", "value2"]] 105 | assert_equal expected_parameters, request_proxy.parameters_for_signature 106 | assert_equal 'POST', request_proxy.method 107 | end 108 | 109 | def test_that_proxy_simple_put_request_works_with_mixed_params 110 | request_proxy = request_proxy(:put, {'key'=>'value'}, {'key2'=>'value2'}) 111 | 112 | expected_parameters = [["key", "value"]] 113 | assert_equal expected_parameters, request_proxy.parameters_for_signature 114 | assert_equal 'PUT', request_proxy.method 115 | end 116 | 117 | def test_that_proxy_simple_patch_request_works_with_mixed_params 118 | request_proxy = request_proxy(:patch, {'key'=>'value'}, {'key2'=>'value2'}) 119 | 120 | expected_parameters = [["key", "value"]] 121 | assert_equal expected_parameters, request_proxy.parameters_for_signature 122 | assert_equal 'PATCH', request_proxy.method 123 | end 124 | 125 | def test_parameter_keys_should_preserve_brackets_from_hash 126 | assert_equal( 127 | [["message[body]", "This is a test"]], 128 | request_proxy(:post, { :message => { :body => 'This is a test' }}).parameters_for_signature 129 | ) 130 | end 131 | 132 | def test_parameter_values_with_amps_should_not_break_parameter_parsing 133 | assert_equal( 134 | [['message[body]', 'http://foo.com/?a=b&c=d']], 135 | request_proxy(:post, { :message => { :body => 'http://foo.com/?a=b&c=d'}}).parameters_for_signature 136 | ) 137 | end 138 | 139 | def test_parameter_keys_should_preserve_brackets_from_array 140 | assert_equal( 141 | [["foo[]", "123"], ["foo[]", "456"]], 142 | request_proxy(:post, { :foo => [123, 456] }).parameters_for_signature.sort 143 | ) 144 | end 145 | 146 | # TODO disabled; ActionController::TestRequest does not appear to parse 147 | # QUERY_STRING 148 | def x_test_query_string_parameter_values_should_be_cgi_unescaped 149 | request = request_proxy do |r| 150 | r.env['QUERY_STRING'] = 'url=http%3A%2F%2Ffoo.com%2F%3Fa%3Db%26c%3Dd' 151 | end 152 | assert_equal( 153 | [['url', 'http://foo.com/?a=b&c=d']], 154 | request.parameters_for_signature.sort 155 | ) 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /test/units/test_em_http_request_proxy.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | begin 4 | 5 | require 'em-http' 6 | require 'oauth/request_proxy/em_http_request' 7 | 8 | 9 | class EmHttpRequestProxyTest < Minitest::Test 10 | 11 | def test_request_proxy_works_with_simple_request 12 | proxy = create_request_proxy 13 | assert_equal({}, proxy.parameters) 14 | end 15 | 16 | def test_request_proxy_works_with_query_string_params 17 | assert_equal({"name" => ["Fred"]}, create_request_proxy(:query => "name=Fred").parameters) 18 | assert_equal({"name" => ["Fred"]}, create_request_proxy(:query => {:name => "Fred"}).parameters) 19 | proxy = create_request_proxy(:query => {:name => "Fred"}, :uri => "http://example.com/?awesome=true") 20 | assert_equal({"name" => ["Fred"], "awesome" => ["true"]}, proxy.parameters) 21 | end 22 | 23 | def test_request_proxy_works_with_post_body_params_with_correct_content_type 24 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "POST" 25 | assert_equal({}, proxy.parameters) 26 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "POST", :body => "a=1" 27 | assert_equal({"a" => ["1"]}, proxy.parameters) 28 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "POST", :body => {"a" => 1} 29 | assert_equal({"a" => ["1"]}, proxy.parameters) 30 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "PUT" 31 | assert_equal({}, proxy.parameters) 32 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "PUT", :body => "a=1" 33 | assert_equal({"a" => ["1"]}, proxy.parameters) 34 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "PUT", :body => {"a" => 1} 35 | assert_equal({"a" => ["1"]}, proxy.parameters) 36 | end 37 | 38 | def test_request_proxy_ignore_post_body_with_invalid_content_type 39 | proxy = create_request_proxy :head => {'Content-Type' => 'text/plain'}, :method => "POST" 40 | assert_equal({}, proxy.parameters) 41 | proxy = create_request_proxy :head => {'Content-Type' => 'text/plain'}, :method => "POST", :body => "a=1" 42 | assert_equal({}, proxy.parameters) 43 | proxy = create_request_proxy :head => {'Content-Type' => 'text/plain'}, :method => "POST", :body => {"a" => 1} 44 | assert_equal({}, proxy.parameters) 45 | proxy = create_request_proxy :head => {'Content-Type' => 'text/plain'}, :method => "PUT" 46 | assert_equal({}, proxy.parameters) 47 | proxy = create_request_proxy :head => {'Content-Type' => 'text/plain'}, :method => "PUT", :body => "a=1" 48 | assert_equal({}, proxy.parameters) 49 | proxy = create_request_proxy :head => {'Content-Type' => 'text/plain'}, :method => "PUT", :body => {"a" => 1} 50 | assert_equal({}, proxy.parameters) 51 | end 52 | 53 | def test_request_proxy_ignores_post_body_with_invalid_method 54 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "DELETE" 55 | assert_equal({}, proxy.parameters) 56 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "DELETE", :body => "a=1" 57 | assert_equal({}, proxy.parameters) 58 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "DELETE", :body => {"a" => 1} 59 | assert_equal({}, proxy.parameters) 60 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "GET" 61 | assert_equal({}, proxy.parameters) 62 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "GET", :body => "a=1" 63 | assert_equal({}, proxy.parameters) 64 | proxy = create_request_proxy :head => {'Content-Type' => 'application/x-www-form-urlencoded'}, :method => "GET", :body => {"a" => 1} 65 | assert_equal({}, proxy.parameters) 66 | end 67 | 68 | def test_request_proxy_works_with_argument_params 69 | assert_equal({"a" => ["1"]}, create_request_proxy(:proxy_options => {:parameters => {"a" => "1"}}).parameters) 70 | end 71 | 72 | def test_request_proxy_works_with_mixed_params 73 | proxy = create_request_proxy(:proxy_options => {:parameters => {"a" => "1"}},:query => {"c" => "1"}, :uri => "http://example.com/test?b=1") 74 | assert_equal({"a" => ["1"], "b" => ["1"], "c" => ["1"]}, proxy.parameters) 75 | proxy = create_request_proxy(:proxy_options => {:parameters => {"a" => "1"}}, :body => {"b" => "1"}, :query => {"c" => "1"}, 76 | :uri => "http://example.com/test?d=1", :method => "POST", :head => {"Content-Type" => "application/x-www-form-urlencoded"}) 77 | assert_equal({"a" => ["1"], "b" => ["1"], "c" => ["1"], "d" => ["1"]}, proxy.parameters) 78 | end 79 | 80 | def test_request_has_the_correct_uri 81 | assert_equal "http://example.com/", create_request_proxy.uri 82 | assert_equal "http://example.com/?a=1", create_request_proxy(:query => "a=1").uri 83 | assert_equal "http://example.com/?a=1", create_request_proxy(:query => {"a" => "1"}).uri 84 | 85 | end 86 | 87 | def test_request_proxy_has_correct_method 88 | assert_equal "GET", create_request_proxy(:method => "GET").method 89 | assert_equal "PUT", create_request_proxy(:method => "PUT").method 90 | assert_equal "POST", create_request_proxy(:method => "POST").method 91 | assert_equal "DELETE", create_request_proxy(:method => "DELETE").method 92 | end 93 | 94 | protected 95 | 96 | def create_client(options = {}) 97 | method = options.delete(:method) || "GET" 98 | uri = options.delete(:uri) || "http://example.com/" 99 | client = EventMachine::HttpClient.new("") 100 | client.uri = URI.parse(uri) 101 | client.method = method.to_s.upcase 102 | client.options = options 103 | client 104 | end 105 | 106 | def create_request_proxy(opts = {}) 107 | arguments = opts.delete(:proxy_options) || {} 108 | OAuth::RequestProxy.proxy(create_client(opts), arguments) 109 | end 110 | 111 | end 112 | 113 | rescue LoadError => e 114 | warn "! problem loading em-http, skipping these tests: #{e}" 115 | end 116 | -------------------------------------------------------------------------------- /lib/oauth/cli/base_command.rb: -------------------------------------------------------------------------------- 1 | class OAuth::CLI 2 | class BaseCommand 3 | def initialize(stdout, stdin, stderr, arguments) 4 | @stdout, @stdin, @stderr = stdout, stdin, stderr 5 | 6 | @options = {} 7 | option_parser.parse!(arguments) 8 | end 9 | 10 | def run 11 | missing = required_options - options.keys 12 | if missing.empty? 13 | _run 14 | else 15 | show_missing(missing) 16 | puts option_parser.help 17 | end 18 | end 19 | 20 | def required_options 21 | [] 22 | end 23 | 24 | protected 25 | 26 | attr_reader :options 27 | 28 | def show_missing(array) 29 | array = array.map { |s| "--#{s}" }.join(' ') 30 | OAuth::CLI.puts_red "Options missing to OAuth CLI: #{array}" 31 | end 32 | 33 | def xmpp? 34 | options[:xmpp] 35 | end 36 | 37 | def verbose? 38 | options[:verbose] 39 | end 40 | 41 | def puts(string=nil) 42 | @stdout.puts(string) 43 | end 44 | 45 | def alert(string=nil) 46 | @stderr.puts(string) 47 | end 48 | 49 | def parameters 50 | @parameters ||= begin 51 | escaped_pairs = options[:params].collect do |pair| 52 | if pair =~ /:/ 53 | Hash[*pair.split(":", 2)].collect do |k,v| 54 | [CGI.escape(k.strip), CGI.escape(v.strip)] * "=" 55 | end 56 | else 57 | pair 58 | end 59 | end 60 | 61 | querystring = escaped_pairs * "&" 62 | cli_params = CGI.parse(querystring) 63 | 64 | { 65 | "oauth_consumer_key" => options[:oauth_consumer_key], 66 | "oauth_nonce" => options[:oauth_nonce], 67 | "oauth_timestamp" => options[:oauth_timestamp], 68 | "oauth_token" => options[:oauth_token], 69 | "oauth_signature_method" => options[:oauth_signature_method], 70 | "oauth_version" => options[:oauth_version] 71 | }.reject { |_k,v| v.nil? || v == "" }.merge(cli_params) 72 | end 73 | end 74 | 75 | def option_parser 76 | @option_parser ||= OptionParser.new do |opts| 77 | opts.banner = "Usage: oauth [ARGS]" 78 | 79 | _option_parser_defaults 80 | _option_parser_common(opts) 81 | _option_parser_sign_and_query(opts) 82 | _option_parser_authorization(opts) 83 | end 84 | end 85 | 86 | def _option_parser_defaults 87 | options[:oauth_nonce] = OAuth::Helper.generate_key 88 | options[:oauth_signature_method] = "HMAC-SHA1" 89 | options[:oauth_timestamp] = OAuth::Helper.generate_timestamp 90 | options[:oauth_version] = "1.0" 91 | options[:method] = :post 92 | options[:params] = [] 93 | options[:scheme] = :header 94 | options[:version] = "1.0" 95 | end 96 | 97 | def _option_parser_common(opts) 98 | ## Common Options 99 | 100 | opts.on("-B", "--body", "Use the request body for OAuth parameters.") do 101 | options[:scheme] = :body 102 | end 103 | 104 | opts.on("--consumer-key KEY", "Specifies the consumer key to use.") do |v| 105 | options[:oauth_consumer_key] = v 106 | end 107 | 108 | opts.on("--consumer-secret SECRET", "Specifies the consumer secret to use.") do |v| 109 | options[:oauth_consumer_secret] = v 110 | end 111 | 112 | opts.on("-H", "--header", "Use the 'Authorization' header for OAuth parameters (default).") do 113 | options[:scheme] = :header 114 | end 115 | 116 | opts.on("-Q", "--query-string", "Use the query string for OAuth parameters.") do 117 | options[:scheme] = :query_string 118 | end 119 | 120 | opts.on("-O", "--options FILE", "Read options from a file") do |v| 121 | arguments = open(v).readlines.map { |l| l.chomp.split(" ") }.flatten 122 | options2 = parse_options(arguments) 123 | options.merge!(options2) 124 | end 125 | end 126 | 127 | def _option_parser_sign_and_query(opts) 128 | opts.separator("\n options for signing and querying") 129 | 130 | opts.on("--method METHOD", "Specifies the method (e.g. GET) to use when signing.") do |v| 131 | options[:method] = v 132 | end 133 | 134 | opts.on("--nonce NONCE", "Specifies the nonce to use.") do |v| 135 | options[:oauth_nonce] = v 136 | end 137 | 138 | opts.on("--parameters PARAMS", "Specifies the parameters to use when signing.") do |v| 139 | options[:params] << v 140 | end 141 | 142 | opts.on("--signature-method METHOD", "Specifies the signature method to use; defaults to HMAC-SHA1.") do |v| 143 | options[:oauth_signature_method] = v 144 | end 145 | 146 | opts.on("--token TOKEN", "Specifies the token to use.") do |v| 147 | options[:oauth_token] = v 148 | end 149 | 150 | opts.on("--secret SECRET", "Specifies the token secret to use.") do |v| 151 | options[:oauth_token_secret] = v 152 | end 153 | 154 | opts.on("--timestamp TIMESTAMP", "Specifies the timestamp to use.") do |v| 155 | options[:oauth_timestamp] = v 156 | end 157 | 158 | opts.on("--realm REALM", "Specifies the realm to use.") do |v| 159 | options[:realm] = v 160 | end 161 | 162 | opts.on("--uri URI", "Specifies the URI to use when signing.") do |v| 163 | options[:uri] = v 164 | end 165 | 166 | opts.on("--version [VERSION]", "Specifies the OAuth version to use.") do |v| 167 | options[:oauth_version] = v 168 | end 169 | 170 | opts.on("--no-version", "Omit oauth_version.") do 171 | options[:oauth_version] = nil 172 | end 173 | 174 | opts.on("--xmpp", "Generate XMPP stanzas.") do 175 | options[:xmpp] = true 176 | options[:method] ||= "iq" 177 | end 178 | 179 | opts.on("-v", "--verbose", "Be verbose.") do 180 | options[:verbose] = true 181 | end 182 | end 183 | 184 | def _option_parser_authorization(opts) 185 | opts.separator("\n options for authorization") 186 | 187 | opts.on("--access-token-url URL", "Specifies the access token URL.") do |v| 188 | options[:access_token_url] = v 189 | end 190 | 191 | opts.on("--authorize-url URL", "Specifies the authorization URL.") do |v| 192 | options[:authorize_url] = v 193 | end 194 | 195 | opts.on("--callback-url URL", "Specifies a callback URL.") do |v| 196 | options[:oauth_callback] = v 197 | end 198 | 199 | opts.on("--request-token-url URL", "Specifies the request token URL.") do |v| 200 | options[:request_token_url] = v 201 | end 202 | 203 | opts.on("--scope SCOPE", "Specifies the scope (Google-specific).") do |v| 204 | options[:scope] = v 205 | end 206 | end 207 | end 208 | end 209 | -------------------------------------------------------------------------------- /HISTORY: -------------------------------------------------------------------------------- 1 | === CURRENT 2 | 3 | * Add metadata to Gemspec file 4 | * Change default timeout to be the same as Net::HTTP default, 60 seconds instead of 30 seconds. 5 | 6 | === 0.5.5 2020-01-19 7 | 8 | * Allow redirect to different host but same path 9 | * Add :allow_empty_params option (#155) 10 | * Fixes ssl-noverify 11 | * Various cleanups 12 | 13 | === 0.5.4 2017-12-08 14 | 15 | * Fixes UnknownRequestType on Rails 5.1 for ActionDispatch::Request (xprazak2) 16 | * Various cleanups (charliesome) 17 | 18 | === 0.5.3 2017-05-24 19 | 20 | * Removing legacy scripts (James Pinto) 21 | * Fix #145 - broken CLI required loading active_support (James Pinto) 22 | 23 | === 0.5.2 2017-05-17 24 | 25 | * Adding a development dependency that had not been mentioned (James Pinto) 26 | * Use assert_nil so as to silence a Minitest 6 deprecation warning (James Pinto) 27 | * Stop bundling tests files in the gem (Michal Papis) 28 | * Minor cleanup on tests (James Pinto) 29 | * TravisCI no longer needs libcurl-dev (James Pinto) 30 | * Nokogiri 1.7 does not accept Ruby 2.0 (James Pinto) 31 | * Upgrading to CodeClimate 1.0 (James Pinto) 32 | * Adding support to Ruby 2.4 and head (James Pinto) 33 | * Locking gemspec to Rails 4 so as to allow our next version for Rails 5 (James Pinto) 34 | * Fix #113 adding paths when a full URL has been specified (James Pinto) 35 | * moving development dependency to gemspec (James Pinto) 36 | * Silencing 'Net::HTTPResponse#header is obsolete' (James Pinto) 37 | * Silencing some test warnings (James Pinto) 38 | * Silencing 'loading in progress, circular require considered harmful' (James Pinto) 39 | * Silence 'URI.escape obsolete' (James Pinto) 40 | * Refactored CLI (James Pinto) 41 | * Bug Fix, webmock 2.0 has introduced a new bug (James Pinto) 42 | * Moving test files into test/units/ (James Pinto) 43 | * Adding CodeClimate (James Pinto) 44 | * Reimplementing #82 - Debug Output Option (James Pinto) 45 | * Making a test/support dir (James Pinto) 46 | * Fix #177 - Adjusting to webmock latest recommended implementation for minitest (James Pinto) 47 | * Adding support to Ruby 2.4 and head (James Pinto) 48 | * Upgrading to CodeClimate 1.0 (James Pinto) 49 | * Nokogiri 1.7 does not accept Ruby 2.0 (James Pinto) 50 | 51 | === 0.5.1 2016-02-29 52 | 53 | * Proper handling for empty query string in RequestToken#build_authorize_url (midchildan, 54 | Harald Sitter) 55 | * Loosen some development dependencies. Add libcurl-dev to travis 56 | * Add license info to the gemspec (Robert Reiz) 57 | * Fixes to travis config. Switch to rubygems for installation and loading 58 | * Remove obsolete comment (Arthur Nogueira Neves) 59 | * Remove jeweler from gemspec 60 | * Replace calls to String#blank? with its implementation (Sergio Gil Pérez de la Manga) 61 | 62 | === 0.5.0 2016-02-20 63 | 64 | * Fix ability to pass in an authorize url with a query string (Roger Smith) 65 | * Add support for HTTP PATCH method (Richard Huang) 66 | * Allow reading private key from a string (Khaja Minhajuddin) 67 | * Fix bug in signature verification (r-stu31) 68 | * Use standard key name (`oauth_token_secret`) in Token#to_query (Craig Walker) 69 | * Fix error in CLI when using `query` without supplying a method (grafikchaos) 70 | * Compatibility fix for Typhoeus >= 0.5.0 (Chad Feller) 71 | * Add rest-client proxy (Khem Veasna) 72 | * Rails 3+ / ActiveSupport::SafeBuffer patch (Clif Reeder) 73 | * Handle `nil` token gracefully for RequestToken#authorize_url (Brian John) 74 | * Replace jeweler with real spec and bundler tasks 75 | * Extract version to separate file 76 | * Fix typhoeus compatibility (Vladimir Mikhailov) 77 | * Use OpenSSL for all digest and hashing. Remove signature methods not defined by OAuth spec. 78 | Add byebug. (Kevin Hughes) 79 | * Fix oauth cli option parser on Ruby 2.2 (Felix Bünemann) 80 | * Change token requests to exclude `oauth_body_hash`. Update doc links in comments. (John Remmen) 81 | * Update gemspec for security fixes. Convert to Minitest. Add .travis.yml. (Kevin Hughes) 82 | * Allow reading certificate file path from environment variable. Add CentOS cert file path 83 | (Danil Vlasov) 84 | * Fix some warnings (amatsuda) 85 | * Various fixes/updates to README (Evan Arnold, Jonathan Camenisch, Brian John, Ankur Sethi) 86 | 87 | === 0.4.7 2012-09-03 88 | 89 | * Fix merging paths if the path is not empty 90 | * Set a configurable timeout for all requests (Rick Olson) 91 | * Fix nested hash params in Consumer#request (Ernie Miller) 92 | 93 | === 0.4.6 2012-04-21 94 | 95 | * Fixed nested attributes in #normalize (Shaliko Usubov) 96 | * Make use the path component of the :site parameter (Jonathon M. Abbott) 97 | * Fixed post body's being dropped in 1.9 (Steven Hammond) 98 | * Fixed PUT request handling (Anton Panasenko) 99 | 100 | === 0.4.5 2011-06-25 101 | 102 | * Add explicit require for rsa/sha1 (Juris Galang) 103 | * Use webmock to mock all http-requests in tests (Adrian Feldman) 104 | * Add gemtest support (Adrian Feldman) 105 | * Fix POST Requests with Typhoeus proxy (niedhui) 106 | * Mention Typhoeus require in the README (Kim Ahlström) 107 | * Fix incorrect hardcoded port (Ian Taylor) 108 | * Use Net::HTTPGenericRequest (Jakub Kuźma) 109 | 110 | === 0.4.4 2010-10-31 111 | 112 | * Fix LoadError rescue in tests: return can't be used in this context (Hans de Graaff) 113 | * HTTP headers should be strings. (seancribbs) 114 | * ensure consumer uri gets set back to original config even if an error occurs (Brian Finney) 115 | * Yahoo uses & to split records in OAuth headers (Brian Finney) 116 | * Added support for Rails 3 in client/action_controller_request (Pelle) 117 | 118 | == 0.4.3 2010-09-01 119 | 120 | * Fix for em-http proxy (ichverstehe) 121 | 122 | == 0.4.2 2010-08-13 123 | 124 | * Fixed compatibility with Ruby 1.9.2 (ecavazos) 125 | * Fixed the em-http request proxy (Joshua Hull) 126 | * Fix for oauth proxy string manipulation (Jakub Suder) 127 | * Added Bundler (rc) Gemfile for easier dev/testing 128 | 129 | == 0.4.1 2010-06-16 130 | 131 | * Added support for using OAuth with proxies (Marsh Gardiner) 132 | * Rails 3 Compatibility fixes (Pelle Braendgaard) 133 | * Fixed load errors on tests for missing (non-required) libraries 134 | 135 | == 0.4.0 2010-04-22 136 | 137 | * Added computation of oauth_body_hash as per OAuth Request Body Hash 1.0 138 | Draft 4 (Michael Reinsch) 139 | * Added the optional `oauth_session_handle` parameter for the Yahoo implementation (Will Bailey) 140 | * Better marshalling implementation (Yoan Blanc) 141 | * Added optional block to OAuth::Consumer.get_*_token (Neill Pearman) 142 | * Exclude `oauth_callback` with :exclude_callback (Neill Pearman) 143 | * Strip extraneous spaces and line breaks from access_token responses 144 | (observed in the wild with Yahoo!'s OAuth+OpenID hybrid) (Eric Hartmann) 145 | * Stop double-escaping PLAINTEXT signatures (Jimmy Zimmerman) 146 | * OAuth::Client::Helper won't override the specified `oauth_version` 147 | (Philip Kromer) 148 | * Support for Ruby 1.9 (Aaron Quint, Corey Donahoe, et al) 149 | * Fixed an encoding / multibyte issue (成田 一生) 150 | * Replaced hoe with Jeweler (Aaron Quint) 151 | * Support for Typhoeus (Bill Kocik) 152 | * Support for em-http (EventMachine) (Darcy Laycock) 153 | * Support for curb (André Luis Leal Cardoso Junior) 154 | * New website (Aaron Quint) 155 | 156 | == 0.3.6 2009-09-14 157 | 158 | * Added -B CLI option to use the :body authentication scheme (Seth) 159 | * Respect `--method` in `authorize` CLI command (Seth) 160 | * Support POST and PUT with raw bodies (Yu-Shan Fung et al) 161 | * Test clean-up (Xavier Shay, Hannes Tydén) 162 | * Added :ca_file consumer option to allow consumer specific certificate 163 | override. (Pelle) 164 | 165 | == 0.3.5 2009-06-03 166 | 167 | * `query` CLI command to access protected resources (Seth) 168 | * Added -H, -Q CLI options for specifying the authentication scheme (Seth) 169 | * Added -O CLI option for specifying a file containing options (Seth) 170 | * Support streamable body contents for large request bodies (Seth Cousins) 171 | * Support for OAuth 1.0a (Seth) 172 | * Added proxy support to OAuth::Consumer (Marshall Huss) 173 | * Added --scope CLI option for Google's 'scope' parameter (Seth) 174 | 175 | == 0.3.4 2009-05-06 176 | 177 | * OAuth::Client::Helper uses OAuth::VERSION (chadisfaction) 178 | * Fix OAuth::RequestProxy::ActionControllerRequest's handling of params 179 | (Tristan Groléat) 180 | 181 | == 0.3.3 2009-05-04 182 | 183 | * Corrected OAuth XMPP namespace (Seth) 184 | * Improved error handling for invalid Authorization headers (Matt Sanford) 185 | * Fixed signatures for non-ASCII under $KCODE other than 'u' (Matt Sanford) 186 | * Fixed edge cases in ActionControllerRequestProxy where params were being 187 | incorrectly signed (Marcos Wright Kuhns) 188 | * Support for arguments in OAuth::Consumer#get_access_token (Matt Sanford) 189 | * Add gem version to user-agent header (Matt Sanford) 190 | * Handle input from aggressive form encoding libraries (Matt Wood) 191 | 192 | == 0.3.2 2009-03-23 193 | 194 | * 2xx statuses should be treated as success (Anders Conbere) 195 | * Support applications using the MethodOverride Rack middleware (László Bácsi) 196 | * `authorize` command for `oauth` CLI (Seth) 197 | * Initial support for Problem Reporting extension (Seth) 198 | * Verify SSL certificates if CA certificates are available (Seth) 199 | * Fixed ActionController parameter escaping behavior (Thiago Arrais, László 200 | Bácsi, Brett Gibson, et al) 201 | * Fixed signature calculation when both options and a block were provided to 202 | OAuth::Signature::Base#initialize (Seth) 203 | * Added help to the 'oauth' CLI (Seth) 204 | * Fixed a problem when attempting to normalize MockRequest URIs (Seth) 205 | 206 | == 0.3.1 2009-1-26 207 | 208 | * Fixed a problem with relative and absolute token request paths. (Michael 209 | Wood) 210 | 211 | == 0.3.0 2009-1-25 212 | 213 | * Support ActionController::Request from Edge Rails (László Bácsi) 214 | * Correctly handle multi-valued parameters (Seth) 215 | * Added #normalized_parameters to OAuth::RequestProxy::Base (Pelle) 216 | * OAuth::Signature.sign and friends now yield the RequestProxy instead of the 217 | token when the passed block's arity is 1. (Seth) 218 | * Token requests are made to the configured URL rather than generating a 219 | potentially incorrect one. (Kellan Elliott-McCrea) 220 | * Command-line app for generating signatures. (Seth) 221 | * Improved test-cases and compatibility for encoding issues. (Pelle) 222 | 223 | == 0.2.7 2008-9-10 The lets fix the last release release 224 | 225 | * Fixed plain text signatures (Andrew Arrow) 226 | * Fixed RSA requests using OAuthTokens. (Philip Lipu Tsai) 227 | 228 | == 0.2.6 2008-9-9 The lets RSA release 229 | 230 | * Improved support for Ruby 1.8.7 (Bill Kocik) 231 | * Fixed RSA verification to support RSA providers 232 | now using Ruby and RSA 233 | * Improved RSA testing 234 | * Omit token when signing with RSA 235 | * Added support for 'private_key_file' option for RSA signatures (Chris Mear) 236 | * Fixed several edge cases where params were being incorrectly signed (Scott 237 | Hill) 238 | * Fixed RSA signing (choonkeat) 239 | 240 | == 0.2.2 2008-2-22 Lets actually support SSL release 241 | 242 | * Use HTTPS when required. 243 | 244 | == 0.2 2008-1-19 All together now release 245 | 246 | This is a big release, where we have merged the efforts of various parties into one common library. 247 | This means there are definitely some API changes you should be aware of. They should be minimal 248 | but please have a look at the unit tests. 249 | 250 | == 0.1.2 2007-12-1 251 | 252 | * Fixed checks for missing OAuth params to improve performance 253 | * Includes Pat's fix for getting the realm out. 254 | 255 | == 0.1.1 2007-11-26 256 | 257 | * First release as a GEM 258 | * Moved all non-Rails functionality from the Rails plugin: 259 | http://code.google.com/p/oauth-plugin/ 260 | -------------------------------------------------------------------------------- /test/units/test_cli.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'oauth/cli' 4 | 5 | class TestCLI < Minitest::Test 6 | 7 | def test_parse 8 | assert_equal 'version', parse('-v') 9 | assert_equal 'version', parse('--version') 10 | 11 | assert_equal 'help', parse('-h') 12 | assert_equal 'help', parse('--help') 13 | assert_equal 'help', parse('-H') 14 | assert_equal 'help', parse('--HELP') 15 | 16 | assert_equal 'help', parse('') 17 | assert_equal 'help', parse(nil) 18 | 19 | assert_equal 'help', parse('NotACommand') 20 | 21 | assert_equal 'help' , parse('h') 22 | assert_equal 'version' , parse('v') 23 | assert_equal 'query' , parse('q') 24 | assert_equal 'authorize' , parse('a') 25 | assert_equal 'sign' , parse('s') 26 | 27 | assert_equal 'help' , parse('help') 28 | assert_equal 'version' , parse('version') 29 | assert_equal 'query' , parse('query') 30 | assert_equal 'authorize' , parse('authorize') 31 | assert_equal 'sign' , parse('sign') 32 | 33 | assert_equal 'help' , parse('H') 34 | assert_equal 'version' , parse('V') 35 | assert_equal 'query' , parse('Q') 36 | assert_equal 'authorize' , parse('A') 37 | assert_equal 'sign' , parse('S') 38 | 39 | assert_equal 'help' , parse('HELP') 40 | assert_equal 'version' , parse('VERSION') 41 | assert_equal 'query' , parse('QUERY') 42 | assert_equal 'authorize' , parse('AUTHORIZE') 43 | assert_equal 'sign' , parse('SIGN') 44 | end 45 | 46 | def test_help_empty 47 | out = run_command 48 | 49 | assert_match(/Usage: /, out) 50 | end 51 | 52 | def test_help 53 | out = run_command(%w[help]) 54 | 55 | assert_match(/Usage: /, out) 56 | end 57 | 58 | def test_version 59 | out = run_command(%w[version]) 60 | 61 | assert_equal "OAuth Gem #{OAuth::VERSION}\n", out 62 | end 63 | 64 | def test_query_empty 65 | out = run_command(%w[query]) 66 | 67 | assert_equal help_output, out 68 | end 69 | 70 | def test_sign_empty 71 | out = run_command(%w[sign]) 72 | 73 | assert_equal help_output, out 74 | end 75 | 76 | def test_authorize_empty 77 | out = run_command(%w[authorize]) 78 | 79 | assert_equal help_output, out 80 | end 81 | 82 | def test_query 83 | consumer = Minitest::Mock.new 84 | access_token = MiniTest::Mock.new 85 | response = MiniTest::Mock.new 86 | 87 | consumer_new = lambda { |oauth_consumer_key, oauth_consumer_secret, options| 88 | expected_options = {:scheme=>:header} 89 | assert_equal 'oauth_consumer_key', oauth_consumer_key 90 | assert_equal 'oauth_consumer_secret', oauth_consumer_secret 91 | assert_equal expected_options, options 92 | consumer 93 | } 94 | access_token_new = lambda { |consumer1, token, secret| 95 | assert_equal consumer1.object_id, consumer.object_id 96 | assert_equal 'TOKEN', token 97 | assert_equal 'SECRET', secret 98 | access_token 99 | } 100 | 101 | # mock expects: 102 | # method return arguments 103 | #------------------------------------------------------------- 104 | response.expect( :code , '!code!') 105 | response.expect( :message , '!message!') 106 | response.expect( :body , '!body!') 107 | access_token.expect(:request , response , [:post, "http://example.com/oauth/url?oauth_consumer_key=oauth_consumer_key&oauth_nonce=GENERATE_KEY&oauth_timestamp=GENERATE_TIMESTAMP&oauth_token=TOKEN&oauth_signature_method=HMAC-SHA1&oauth_version=1.0"]) 108 | 109 | OAuth::Helper.stub(:generate_key, 'GENERATE_KEY') do 110 | OAuth::Helper.stub(:generate_timestamp, 'GENERATE_TIMESTAMP') do 111 | OAuth::AccessToken.stub(:new, access_token_new) do 112 | OAuth::Consumer.stub(:new, consumer_new) do 113 | out = run_command %w[query 114 | --consumer-key oauth_consumer_key 115 | --consumer-secret oauth_consumer_secret 116 | --token TOKEN 117 | --secret SECRET 118 | --uri http://example.com/oauth/url 119 | ] 120 | 121 | assert_equal out, <<-EXPECTED 122 | http://example.com/oauth/url?oauth_consumer_key=oauth_consumer_key&oauth_nonce=GENERATE_KEY&oauth_timestamp=GENERATE_TIMESTAMP&oauth_token=TOKEN&oauth_signature_method=HMAC-SHA1&oauth_version=1.0 123 | !code! !message! 124 | !body! 125 | EXPECTED 126 | end 127 | end 128 | end 129 | end 130 | end 131 | 132 | def test_authorize 133 | access_token = MiniTest::Mock.new 134 | consumer = MiniTest::Mock.new 135 | request_token = MiniTest::Mock.new 136 | 137 | consumer_new = lambda { |oauth_consumer_key, oauth_consumer_secret, options| 138 | expected_options = {:access_token_url=>nil, :authorize_url=>nil, :request_token_url=>nil, :scheme=>:header, :http_method=>:get} 139 | assert_equal 'oauth_consumer_key', oauth_consumer_key 140 | assert_equal 'oauth_consumer_secret', oauth_consumer_secret 141 | assert_equal expected_options, options 142 | consumer 143 | } 144 | 145 | # mock expects: 146 | # method return arguments 147 | #---------------------------------------------------------------------- 148 | access_token.expect( :params , {}) 149 | consumer.expect( :get_request_token , request_token , [{:oauth_callback=>nil} , {}]) 150 | request_token.expect( :callback_confirmed? , false) 151 | request_token.expect( :authorize_url , "!url1!") 152 | request_token.expect( :get_access_token , access_token, [{:oauth_verifier=>nil}]) 153 | 154 | OAuth::Helper.stub(:generate_key, 'GENERATE_KEY') do 155 | OAuth::Helper.stub(:generate_timestamp, 'GENERATE_TIMESTAMP') do 156 | OAuth::Consumer.stub(:new, consumer_new) do 157 | 158 | out = run_command %w[authorize 159 | --consumer-key oauth_consumer_key 160 | --consumer-secret oauth_consumer_secret 161 | --method GET 162 | --uri http://example.com/oauth/url 163 | ] 164 | 165 | assert_equal out, <<-EXPECTED 166 | Please visit this url to authorize: 167 | !url1! 168 | Press return to continue... 169 | Response: 170 | EXPECTED 171 | end 172 | end 173 | end 174 | end 175 | 176 | def test_sign 177 | access_token = MiniTest::Mock.new 178 | consumer = MiniTest::Mock.new 179 | request_token = MiniTest::Mock.new 180 | 181 | consumer_new = lambda { |oauth_consumer_key, oauth_consumer_secret, options| 182 | expected_options = {:access_token_url=>nil, :authorize_url=>nil, :request_token_url=>nil, :scheme=>:header, :http_method=>:get} 183 | assert_equal 'oauth_consumer_key', oauth_consumer_key 184 | assert_equal 'oauth_consumer_secret', oauth_consumer_secret 185 | assert_equal expected_options, options 186 | consumer 187 | } 188 | 189 | # mock expects: 190 | # method return arguments 191 | #---------------------------------------------------------------------- 192 | access_token.expect( :params , {}) 193 | consumer.expect( :get_request_token , request_token , [{:oauth_callback=>nil} , {}]) 194 | request_token.expect( :callback_confirmed? , false) 195 | request_token.expect( :authorize_url , "!url1!") 196 | request_token.expect( :get_access_token , access_token, [{:oauth_verifier=>nil}]) 197 | 198 | out = [] 199 | 200 | OAuth::Helper.stub(:generate_key, 'GENERATE_KEY') do 201 | OAuth::Helper.stub(:generate_timestamp, 'GENERATE_TIMESTAMP') do 202 | OAuth::Consumer.stub(:new, consumer_new) do 203 | 204 | out.push run_command %w[sign 205 | --consumer-key oauth_consumer_key 206 | --consumer-secret oauth_consumer_secret 207 | --method GET 208 | --token TOKEN 209 | --secret SECRET 210 | --uri http://example.com/oauth/url 211 | -v 212 | ] 213 | 214 | out.push run_command %w[sign 215 | --consumer-key oauth_consumer_key 216 | --consumer-secret oauth_consumer_secret 217 | --method GET 218 | --token TOKEN 219 | --secret SECRET 220 | --uri http://example.com/oauth/url 221 | ] 222 | end 223 | end 224 | end 225 | 226 | assert_equal out.pop, <<-EXPECTED 227 | MujZyJYT5ix2s388yF8sExvPIgA= 228 | EXPECTED 229 | 230 | assert_equal out.pop, <<-EXPECTED 231 | OAuth parameters: 232 | oauth_consumer_key: oauth_consumer_key 233 | oauth_nonce: GENERATE_KEY 234 | oauth_timestamp: GENERATE_TIMESTAMP 235 | oauth_token: TOKEN 236 | oauth_signature_method: HMAC-SHA1 237 | oauth_version: 1.0 238 | 239 | Method: GET 240 | URI: http://example.com/oauth/url 241 | Normalized params: oauth_consumer_key=oauth_consumer_key&oauth_nonce=GENERATE_KEY&oauth_signature_method=HMAC-SHA1&oauth_timestamp=GENERATE_TIMESTAMP&oauth_token=TOKEN&oauth_version=1.0 242 | Signature base string: GET&http%3A%2F%2Fexample.com%2Foauth%2Furl&oauth_consumer_key%3Doauth_consumer_key%26oauth_nonce%3DGENERATE_KEY%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3DGENERATE_TIMESTAMP%26oauth_token%3DTOKEN%26oauth_version%3D1.0 243 | OAuth Request URI: http://example.com/oauth/url?oauth_consumer_key=oauth_consumer_key&oauth_nonce=GENERATE_KEY&oauth_signature=MujZyJYT5ix2s388yF8sExvPIgA%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=GENERATE_TIMESTAMP&oauth_token=TOKEN&oauth_version=1.0 244 | Request URI: http://example.com/oauth/url? 245 | Authorization header: OAuth oauth_consumer_key=\"oauth_consumer_key\", oauth_nonce=\"GENERATE_KEY\", oauth_timestamp=\"GENERATE_TIMESTAMP\", oauth_token=\"TOKEN\", oauth_signature_method=\"HMAC-SHA1\", oauth_version=\"1.0\", oauth_signature=\"MujZyJYT5ix2s388yF8sExvPIgA%3D\" 246 | Signature: MujZyJYT5ix2s388yF8sExvPIgA= 247 | Escaped signature: MujZyJYT5ix2s388yF8sExvPIgA%3D 248 | EXPECTED 249 | 250 | end 251 | 252 | 253 | 254 | 255 | private 256 | 257 | def run_command(arguments=[]) 258 | s = StringIO.new 259 | command = arguments.shift 260 | OAuth::CLI.new(s, StringIO.new, StringIO.new, command, arguments).run 261 | 262 | s.rewind 263 | s.read 264 | end 265 | 266 | def parse(command) 267 | cli = OAuth::CLI.new(StringIO.new, StringIO.new, StringIO.new, command, []) 268 | cli.send(:parse_command, command) 269 | end 270 | 271 | def help_output 272 | <<-EXPECTED 273 | Usage: oauth [ARGS] 274 | -B, --body Use the request body for OAuth parameters. 275 | --consumer-key KEY Specifies the consumer key to use. 276 | --consumer-secret SECRET Specifies the consumer secret to use. 277 | -H, --header Use the 'Authorization' header for OAuth parameters (default). 278 | -Q, --query-string Use the query string for OAuth parameters. 279 | -O, --options FILE Read options from a file 280 | 281 | options for signing and querying 282 | --method METHOD Specifies the method (e.g. GET) to use when signing. 283 | --nonce NONCE Specifies the nonce to use. 284 | --parameters PARAMS Specifies the parameters to use when signing. 285 | --signature-method METHOD Specifies the signature method to use; defaults to HMAC-SHA1. 286 | --token TOKEN Specifies the token to use. 287 | --secret SECRET Specifies the token secret to use. 288 | --timestamp TIMESTAMP Specifies the timestamp to use. 289 | --realm REALM Specifies the realm to use. 290 | --uri URI Specifies the URI to use when signing. 291 | --version [VERSION] Specifies the OAuth version to use. 292 | --no-version Omit oauth_version. 293 | --xmpp Generate XMPP stanzas. 294 | -v, --verbose Be verbose. 295 | 296 | options for authorization 297 | --access-token-url URL Specifies the access token URL. 298 | --authorize-url URL Specifies the authorization URL. 299 | --callback-url URL Specifies a callback URL. 300 | --request-token-url URL Specifies the request token URL. 301 | --scope SCOPE Specifies the scope (Google-specific). 302 | EXPECTED 303 | end 304 | end 305 | -------------------------------------------------------------------------------- /test/units/test_consumer.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | # This performs testing against Andy Smith's test server http://term.ie/oauth/example/ 4 | # Thanks Andy. 5 | # This also means you have to be online to be able to run these. 6 | class ConsumerTest < Minitest::Test 7 | def setup 8 | @consumer=OAuth::Consumer.new( 9 | 'consumer_key_86cad9', '5888bf0345e5d237', 10 | { 11 | :site=>"http://blabla.bla", 12 | :proxy=>"http://user:password@proxy.bla:8080", 13 | :request_token_path=>"/oauth/example/request_token.php", 14 | :access_token_path=>"/oauth/example/access_token.php", 15 | :authorize_path=>"/oauth/example/authorize.php", 16 | :scheme=>:header, 17 | :http_method=>:get 18 | }) 19 | @token = OAuth::ConsumerToken.new(@consumer,'token_411a7f', '3196ffd991c8ebdb') 20 | @request_uri = URI.parse('http://example.com/test?key=value') 21 | @request_parameters = { 'key' => 'value' } 22 | @nonce = 225579211881198842005988698334675835446 23 | @timestamp = "1199645624" 24 | @consumer.http=Net::HTTP.new(@request_uri.host, @request_uri.port) 25 | end 26 | 27 | def test_initializer 28 | assert_equal "consumer_key_86cad9",@consumer.key 29 | assert_equal "5888bf0345e5d237",@consumer.secret 30 | assert_equal "http://blabla.bla",@consumer.site 31 | assert_equal "http://user:password@proxy.bla:8080",@consumer.proxy 32 | assert_equal "/oauth/example/request_token.php",@consumer.request_token_path 33 | assert_equal "/oauth/example/access_token.php",@consumer.access_token_path 34 | assert_equal "http://blabla.bla/oauth/example/request_token.php",@consumer.request_token_url 35 | assert_equal "http://blabla.bla/oauth/example/access_token.php",@consumer.access_token_url 36 | assert_equal "http://blabla.bla/oauth/example/authorize.php",@consumer.authorize_url 37 | assert_equal :header,@consumer.scheme 38 | assert_equal :get,@consumer.http_method 39 | assert_nil @consumer.debug_output 40 | end 41 | 42 | def test_defaults 43 | @consumer=OAuth::Consumer.new( 44 | "key", 45 | "secret", 46 | { 47 | :site=>"http://twitter.com" 48 | }) 49 | assert_equal "key",@consumer.key 50 | assert_equal "secret",@consumer.secret 51 | assert_equal "http://twitter.com",@consumer.site 52 | assert_nil @consumer.proxy 53 | assert_equal "/oauth/request_token",@consumer.request_token_path 54 | assert_equal "/oauth/access_token",@consumer.access_token_path 55 | assert_equal "http://twitter.com/oauth/request_token",@consumer.request_token_url 56 | assert_equal "http://twitter.com/oauth/access_token",@consumer.access_token_url 57 | assert_equal "http://twitter.com/oauth/authorize",@consumer.authorize_url 58 | assert_equal :header,@consumer.scheme 59 | assert_equal :post,@consumer.http_method 60 | assert_nil @consumer.debug_output 61 | end 62 | 63 | def test_debug_output_true 64 | @consumer=OAuth::Consumer.new( 65 | "key", 66 | "secret", 67 | { 68 | :debug_output=>true 69 | }) 70 | assert_equal $stdout,@consumer.debug_output 71 | end 72 | 73 | def test_debug_output 74 | stringio = StringIO.new 75 | @consumer=OAuth::Consumer.new( 76 | "key", 77 | "secret", 78 | { 79 | :debug_output=>stringio 80 | }) 81 | assert_equal stringio,@consumer.debug_output 82 | end 83 | 84 | def test_site_without_path 85 | @consumer=OAuth::Consumer.new( 86 | "key", 87 | "secret", 88 | { 89 | :site=>"http://twitter.com" 90 | }) 91 | request = stub(:oauth! => nil) 92 | http = stub(:request => stub(:to_hash => {}), :address => "identi.ca") 93 | Net::HTTP::Get.expects(:new).with('/people', {}).returns(request) 94 | @consumer.expects(:create_http).returns(http) 95 | @consumer.request(:get, '/people', nil, {}) 96 | end 97 | 98 | def test_site_with_path 99 | @consumer=OAuth::Consumer.new( 100 | "key", 101 | "secret", 102 | { 103 | :site=>"http://identi.ca/api" 104 | }) 105 | request = stub(:oauth! => nil) 106 | http = stub(:request => stub(:to_hash => {}), :address => "identi.ca") 107 | Net::HTTP::Get.expects(:new).with('/api/people', {}).returns(request) 108 | @consumer.expects(:create_http).returns(http) 109 | @consumer.request(:get, '/people', nil, {}) 110 | end 111 | 112 | def test_post_of_nested_params_maintains_nesting 113 | @consumer=OAuth::Consumer.new( 114 | "key", 115 | "secret", 116 | { 117 | :site=>"http://twitter.com" 118 | }) 119 | request = @consumer.create_signed_request( 120 | :post, 121 | '/people', 122 | nil, 123 | {}, 124 | { 125 | :key => { 126 | :subkey => 'value' 127 | } 128 | }) 129 | assert_equal 'key%5Bsubkey%5D=value', request.body 130 | assert_equal request.content_type, 'application/x-www-form-urlencoded' 131 | end 132 | 133 | def test_override_paths 134 | @consumer=OAuth::Consumer.new( 135 | "key", 136 | "secret", 137 | { 138 | :site=>"http://twitter.com", 139 | :request_token_url=>"http://oauth.twitter.com/request_token", 140 | :access_token_url=>"http://oauth.twitter.com/access_token", 141 | :authorize_url=>"http://site.twitter.com/authorize" 142 | }) 143 | assert_equal "key",@consumer.key 144 | assert_equal "secret",@consumer.secret 145 | assert_equal "http://twitter.com",@consumer.site 146 | assert_equal "/oauth/request_token",@consumer.request_token_path 147 | assert_equal "/oauth/access_token",@consumer.access_token_path 148 | assert_equal "http://oauth.twitter.com/request_token",@consumer.request_token_url 149 | assert_equal "http://oauth.twitter.com/access_token",@consumer.access_token_url 150 | assert_equal "http://site.twitter.com/authorize",@consumer.authorize_url 151 | assert_equal :header,@consumer.scheme 152 | assert_equal :post,@consumer.http_method 153 | end 154 | 155 | def test_getting_tokens_doesnt_add_paths_if_full_url_is_specified 156 | @consumer = OAuth::Consumer.new( 157 | "key", 158 | "secret", 159 | { 160 | :site => "https://api.mysite.co.nz/v1", 161 | :request_token_url => "https://authentication.mysite.co.nz/Oauth/RequestToken" 162 | }) 163 | 164 | stub_request(:post, "https://authentication.mysite.co.nz/Oauth/RequestToken").to_return(:body => "success", :status => 200) 165 | @consumer.get_request_token 166 | end 167 | 168 | def test_noverify_true 169 | @consumer = OAuth::Consumer.new( 170 | "key", 171 | "secret", 172 | { 173 | :site => "https://api.mysite.co.nz/v1", 174 | :request_token_url => "https://authentication.mysite.co.nz/Oauth/RequestToken", 175 | :no_verify => true 176 | }) 177 | 178 | stub_request(:post, "https://authentication.mysite.co.nz/Oauth/RequestToken").to_return(:body => "success", :status => 200) 179 | 180 | Net::HTTP.any_instance.expects(:'verify_mode=').with(OpenSSL::SSL::VERIFY_NONE) 181 | 182 | @consumer.get_request_token 183 | end 184 | 185 | def test_noverify_false 186 | @consumer = OAuth::Consumer.new( 187 | "key", 188 | "secret", 189 | { 190 | :site => "https://api.mysite.co.nz/v1", 191 | :request_token_url => "https://authentication.mysite.co.nz/Oauth/RequestToken", 192 | :no_verify => false 193 | }) 194 | 195 | stub_request(:post, "https://authentication.mysite.co.nz/Oauth/RequestToken").to_return(:body => "success", :status => 200) 196 | 197 | Net::HTTP.any_instance.expects(:'verify_mode=').with(OpenSSL::SSL::VERIFY_PEER) 198 | @consumer.get_request_token 199 | end 200 | 201 | def test_noverify_empty 202 | @consumer = OAuth::Consumer.new( 203 | "key", 204 | "secret", 205 | { 206 | :site => "https://api.mysite.co.nz/v1", 207 | :request_token_url => "https://authentication.mysite.co.nz/Oauth/RequestToken" 208 | }) 209 | 210 | stub_request(:post, "https://authentication.mysite.co.nz/Oauth/RequestToken").to_return(:body => "success", :status => 200) 211 | 212 | Net::HTTP.any_instance.expects(:'verify_mode=').with(OpenSSL::SSL::VERIFY_PEER) 213 | @consumer.get_request_token 214 | end 215 | 216 | def test_token_request_identifies_itself_as_a_token_request 217 | request_options = {} 218 | @consumer.stubs(:request).returns(create_stub_http_response) 219 | @consumer.token_request(:post, '/', 'token', request_options) {} 220 | assert_equal true, request_options[:token_request] 221 | end 222 | 223 | def test_that_token_response_should_be_uri_parameter_format_as_default 224 | @consumer.expects(:request).returns(create_stub_http_response("oauth_token=token&oauth_token_secret=secret")) 225 | 226 | hash = @consumer.token_request(:get, "") 227 | 228 | assert_equal "token", hash[:oauth_token] 229 | assert_equal "secret", hash[:oauth_token_secret] 230 | end 231 | 232 | def test_can_provided_a_block_to_interpret_token_response 233 | @consumer.expects(:request).returns(create_stub_http_response) 234 | 235 | hash = @consumer.token_request(:get, '') {{ :oauth_token => 'token', :oauth_token_secret => 'secret' }} 236 | 237 | assert_equal 'token', hash[:oauth_token] 238 | assert_equal 'secret', hash[:oauth_token_secret] 239 | end 240 | 241 | def test_token_request_follows_redirect 242 | redirect_url = @request_uri.clone 243 | redirect_url.path = "/oauth/example/request_token_redirect.php" 244 | stub_request(:get, /.*#{@request_uri.path}/).to_return(:status => 301, :headers => {'Location' => redirect_url.to_s}) 245 | stub_request(:get, /.*#{redirect_url.path}/).to_return(:body => "oauth_token=token&oauth_token_secret=secret") 246 | 247 | hash = @consumer.token_request(:get, @request_uri.path) {{ :oauth_token => 'token', :oauth_token_secret => 'secret' }} 248 | 249 | assert_equal 'token', hash[:oauth_token] 250 | assert_equal 'secret', hash[:oauth_token_secret] 251 | end 252 | 253 | def test_follow_redirect_different_host_same_path 254 | request_uri = URI.parse("https://example.com/request_token") 255 | redirect_uri = URI.parse("https://foobar.com/request_token") 256 | 257 | stub_request(:get, "http://example.com/request_token").to_return(:status => 301, :headers => {'Location' => redirect_uri.to_s}) 258 | stub_request(:get, "https://foobar.com/request_token").to_return(:body => "oauth_token=token&oauth_token_secret=secret") 259 | 260 | hash = @consumer.token_request(:get, request_uri.path) {{ :oauth_token => 'token', :oauth_token_secret => 'secret' }} 261 | 262 | assert_equal 'token', hash[:oauth_token] 263 | assert_equal 'secret', hash[:oauth_token_secret] 264 | end 265 | 266 | def test_that_can_provide_a_block_to_interpret_a_request_token_response 267 | @consumer.expects(:request).returns(create_stub_http_response) 268 | 269 | token = @consumer.get_request_token {{ :oauth_token => 'token', :oauth_token_secret => 'secret' }} 270 | 271 | assert_equal 'token', token.token 272 | assert_equal 'secret', token.secret 273 | end 274 | 275 | def test_that_block_is_not_mandatory_for_getting_an_access_token 276 | stub_token = mock 277 | @consumer.expects(:request).returns(create_stub_http_response("oauth_token=token&oauth_token_secret=secret")) 278 | 279 | token = @consumer.get_access_token(stub_token) 280 | 281 | assert_equal 'token', token.token 282 | assert_equal 'secret', token.secret 283 | end 284 | 285 | def test_that_can_provide_a_block_to_interpret_an_access_token_response 286 | stub_token = mock 287 | @consumer.expects(:request).returns(create_stub_http_response) 288 | 289 | token = @consumer.get_access_token(stub_token) {{ :oauth_token => 'token', :oauth_token_secret => 'secret' }} 290 | 291 | assert_equal 'token', token.token 292 | assert_equal 'secret', token.secret 293 | end 294 | 295 | def test_that_not_setting_ignore_callback_will_include_oauth_callback_in_request_options 296 | request_options = {} 297 | @consumer.stubs(:request).returns(create_stub_http_response) 298 | 299 | @consumer.get_request_token(request_options) {{ :oauth_token => 'token', :oauth_token_secret => 'secret' }} 300 | 301 | assert_equal 'oob', request_options[:oauth_callback] 302 | end 303 | 304 | def test_that_setting_ignore_callback_will_exclude_oauth_callback_in_request_options 305 | request_options = { :exclude_callback=> true } 306 | @consumer.stubs(:request).returns(create_stub_http_response) 307 | 308 | @consumer.get_request_token(request_options) {{ :oauth_token => 'token', :oauth_token_secret => 'secret' }} 309 | 310 | assert_nil request_options[:oauth_callback] 311 | end 312 | 313 | private 314 | 315 | def create_stub_http_response expected_body=nil 316 | stub_http_response = stub 317 | stub_http_response.stubs(:code).returns(200) 318 | stub_http_response.stubs(:body).tap {|expectation| expectation.returns(expected_body) unless expected_body.nil? } 319 | return stub_http_response 320 | end 321 | end 322 | -------------------------------------------------------------------------------- /test/integration/consumer_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | module Integration 4 | class ConsumerTest < Minitest::Test 5 | def setup 6 | @consumer=OAuth::Consumer.new( 7 | 'consumer_key_86cad9', '5888bf0345e5d237', 8 | { 9 | :site=>"http://blabla.bla", 10 | :proxy=>"http://user:password@proxy.bla:8080", 11 | :request_token_path=>"/oauth/example/request_token.php", 12 | :access_token_path=>"/oauth/example/access_token.php", 13 | :authorize_path=>"/oauth/example/authorize.php", 14 | :scheme=>:header, 15 | :http_method=>:get 16 | }) 17 | @token = OAuth::ConsumerToken.new(@consumer,'token_411a7f', '3196ffd991c8ebdb') 18 | @request_uri = URI.parse('http://example.com/test?key=value') 19 | @request_parameters = { 'key' => 'value' } 20 | @nonce = 225579211881198842005988698334675835446 21 | @timestamp = "1199645624" 22 | @consumer.http=Net::HTTP.new(@request_uri.host, @request_uri.port) 23 | end 24 | 25 | def test_that_signing_auth_headers_on_get_requests_works 26 | request = Net::HTTP::Get.new(@request_uri.path + "?" + request_parameters_to_s) 27 | @token.sign!(request, {:nonce => @nonce, :timestamp => @timestamp}) 28 | 29 | assert_equal 'GET', request.method 30 | assert_equal '/test?key=value', request.path 31 | assert_equal "OAuth oauth_nonce=\"225579211881198842005988698334675835446\", oauth_signature_method=\"HMAC-SHA1\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"1oO2izFav1GP4kEH2EskwXkCRFg%3D\", oauth_version=\"1.0\"".delete(',').split.sort, request['authorization'].delete(',').split.sort 32 | end 33 | 34 | def test_that_setting_signature_method_on_consumer_effects_signing 35 | require 'oauth/signature/plaintext' 36 | request = Net::HTTP::Get.new(@request_uri.path) 37 | consumer = @consumer.dup 38 | consumer.options[:signature_method] = 'PLAINTEXT' 39 | token = OAuth::ConsumerToken.new(consumer, 'token_411a7f', '3196ffd991c8ebdb') 40 | token.sign!(request, {:nonce => @nonce, :timestamp => @timestamp}) 41 | 42 | refute_match( /oauth_signature_method="HMAC-SHA1"/, request['authorization']) 43 | assert_match( /oauth_signature_method="PLAINTEXT"/, request['authorization']) 44 | end 45 | 46 | def test_that_setting_signature_method_on_consumer_effects_signature_base_string 47 | require 'oauth/signature/plaintext' 48 | request = Net::HTTP::Get.new(@request_uri.path) 49 | consumer = @consumer.dup 50 | consumer.options[:signature_method] = 'PLAINTEXT' 51 | 52 | request = Net::HTTP::Get.new('/') 53 | signature_base_string = consumer.signature_base_string(request) 54 | 55 | refute_match( /HMAC-SHA1/, signature_base_string) 56 | assert_equal( "#{consumer.secret}&", signature_base_string) 57 | end 58 | 59 | def test_that_plaintext_signature_works 60 | # Invalid test because server expects double-escaped signature 61 | require 'oauth/signature/plaintext' 62 | # consumer = OAuth::Consumer.new("key", "secret", 63 | # :site => "http://term.ie", :signature_method => 'PLAINTEXT') 64 | # access_token = OAuth::AccessToken.new(consumer, 'accesskey', 'accesssecret') 65 | # response = access_token.get("/oauth/example/echo_api.php?echo=hello") 66 | 67 | # assert_equal 'echo=hello', response.body 68 | end 69 | 70 | def test_that_signing_auth_headers_on_post_requests_works 71 | request = Net::HTTP::Post.new(@request_uri.path) 72 | request.set_form_data( @request_parameters ) 73 | @token.sign!(request, {:nonce => @nonce, :timestamp => @timestamp}) 74 | # assert_equal "",request.oauth_helper.signature_base_string 75 | 76 | assert_equal 'POST', request.method 77 | assert_equal '/test', request.path 78 | assert_equal 'key=value', request.body 79 | assert_equal "OAuth oauth_nonce=\"225579211881198842005988698334675835446\", oauth_signature_method=\"HMAC-SHA1\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"26g7wHTtNO6ZWJaLltcueppHYiI%3D\", oauth_version=\"1.0\"".delete(',').split.sort, request['authorization'].delete(',').split.sort 80 | end 81 | 82 | def test_that_signing_post_params_works 83 | request = Net::HTTP::Post.new(@request_uri.path) 84 | request.set_form_data( @request_parameters ) 85 | @token.sign!(request, {:scheme => 'body', :nonce => @nonce, :timestamp => @timestamp}) 86 | 87 | assert_equal 'POST', request.method 88 | assert_equal '/test', request.path 89 | assert_match(/key=value&oauth_consumer_key=consumer_key_86cad9&oauth_nonce=225579211881198842005988698334675835446&oauth_signature=26g7wHTtNO6ZWJaLltcueppHYiI%3[Dd]&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1199645624&oauth_token=token_411a7f&oauth_version=1.0/, request.body.split("&").sort.join("&")) 90 | assert_nil request['authorization'] 91 | end 92 | 93 | def test_that_using_auth_headers_on_get_on_create_signed_requests_works 94 | request=@consumer.create_signed_request(:get,@request_uri.path+ "?" + request_parameters_to_s,@token,{:nonce => @nonce, :timestamp => @timestamp},@request_parameters) 95 | 96 | assert_equal 'GET', request.method 97 | assert_equal '/test?key=value', request.path 98 | assert_equal "OAuth oauth_nonce=\"225579211881198842005988698334675835446\", oauth_signature_method=\"HMAC-SHA1\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"1oO2izFav1GP4kEH2EskwXkCRFg%3D\", oauth_version=\"1.0\"".delete(',').split.sort, request['authorization'].delete(',').split.sort 99 | end 100 | 101 | def test_that_using_auth_headers_on_post_on_create_signed_requests_works 102 | request=@consumer.create_signed_request(:post,@request_uri.path,@token,{:nonce => @nonce, :timestamp => @timestamp},@request_parameters,{}) 103 | assert_equal 'POST', request.method 104 | assert_equal '/test', request.path 105 | assert_equal 'key=value', request.body 106 | assert_equal "OAuth oauth_nonce=\"225579211881198842005988698334675835446\", oauth_signature_method=\"HMAC-SHA1\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"26g7wHTtNO6ZWJaLltcueppHYiI%3D\", oauth_version=\"1.0\"".delete(',').split.sort, request['authorization'].delete(',').split.sort 107 | end 108 | 109 | def test_that_signing_post_params_works_2 110 | request=@consumer.create_signed_request(:post,@request_uri.path,@token,{:scheme => 'body', :nonce => @nonce, :timestamp => @timestamp},@request_parameters,{}) 111 | 112 | assert_equal 'POST', request.method 113 | assert_equal '/test', request.path 114 | assert_match(/key=value&oauth_consumer_key=consumer_key_86cad9&oauth_nonce=225579211881198842005988698334675835446&oauth_signature=26g7wHTtNO6ZWJaLltcueppHYiI%3[Dd]&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1199645624&oauth_token=token_411a7f&oauth_version=1.0/, request.body.split("&").sort.join("&")) 115 | assert_nil request['authorization'] 116 | end 117 | 118 | def test_step_by_step_token_request 119 | stub_test_ie 120 | 121 | @consumer=OAuth::Consumer.new( 122 | "key", 123 | "secret", 124 | { 125 | :site=>"http://term.ie", 126 | :request_token_path=>"/oauth/example/request_token.php", 127 | :access_token_path=>"/oauth/example/access_token.php", 128 | :authorize_path=>"/oauth/example/authorize.php", 129 | :scheme=>:header 130 | }) 131 | options={:nonce=>'nonce',:timestamp=>Time.now.to_i.to_s} 132 | 133 | request = Net::HTTP::Get.new("/oauth/example/request_token.php") 134 | signature_base_string=@consumer.signature_base_string(request,nil,options) 135 | assert_equal "GET&http%3A%2F%2Fterm.ie%2Foauth%2Fexample%2Frequest_token.php&oauth_consumer_key%3Dkey%26oauth_nonce%3D#{options[:nonce]}%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D#{options[:timestamp]}%26oauth_version%3D1.0",signature_base_string 136 | @consumer.sign!(request, nil,options) 137 | 138 | assert_equal 'GET', request.method 139 | assert_nil request.body 140 | response=@consumer.http.request(request) 141 | assert_equal "200",response.code 142 | assert_equal "oauth_token=requestkey&oauth_token_secret=requestsecret",response.body 143 | end 144 | 145 | def test_get_token_sequence 146 | stub_test_ie 147 | 148 | @consumer=OAuth::Consumer.new( 149 | "key", 150 | "secret", 151 | { 152 | :site=>"http://term.ie", 153 | :request_token_path=>"/oauth/example/request_token.php", 154 | :access_token_path=>"/oauth/example/access_token.php", 155 | :authorize_path=>"/oauth/example/authorize.php" 156 | }) 157 | assert_equal "http://term.ie/oauth/example/request_token.php",@consumer.request_token_url 158 | assert_equal "http://term.ie/oauth/example/access_token.php",@consumer.access_token_url 159 | 160 | assert !@consumer.request_token_url?, "Should not use fully qualified request token url" 161 | assert !@consumer.access_token_url?, "Should not use fully qualified access token url" 162 | assert !@consumer.authorize_url?, "Should not use fully qualified url" 163 | 164 | @request_token=@consumer.get_request_token 165 | assert @request_token 166 | assert_equal "requestkey",@request_token.token 167 | assert_equal "requestsecret",@request_token.secret 168 | assert_equal "http://term.ie/oauth/example/authorize.php?oauth_token=requestkey",@request_token.authorize_url 169 | 170 | @access_token=@request_token.get_access_token 171 | assert @access_token 172 | assert_equal "accesskey",@access_token.token 173 | assert_equal "accesssecret",@access_token.secret 174 | 175 | @response=@access_token.get("/oauth/example/echo_api.php?ok=hello&test=this") 176 | assert @response 177 | assert_equal "200",@response.code 178 | assert_equal( "ok=hello&test=this",@response.body) 179 | 180 | @response=@access_token.post("/oauth/example/echo_api.php",{'ok'=>'hello','test'=>'this'}) 181 | assert @response 182 | assert_equal "200",@response.code 183 | assert_equal( "ok=hello&test=this",@response.body) 184 | end 185 | 186 | def test_get_token_sequence_using_fqdn 187 | stub_test_ie 188 | 189 | @consumer=OAuth::Consumer.new( 190 | "key", 191 | "secret", 192 | { 193 | :site=>"http://term.ie", 194 | :request_token_url=>"http://term.ie/oauth/example/request_token.php", 195 | :access_token_url=>"http://term.ie/oauth/example/access_token.php", 196 | :authorize_url=>"http://term.ie/oauth/example/authorize.php" 197 | }) 198 | assert_equal "http://term.ie/oauth/example/request_token.php",@consumer.request_token_url 199 | assert_equal "http://term.ie/oauth/example/access_token.php",@consumer.access_token_url 200 | 201 | assert @consumer.request_token_url?, "Should use fully qualified request token url" 202 | assert @consumer.access_token_url?, "Should use fully qualified access token url" 203 | assert @consumer.authorize_url?, "Should use fully qualified url" 204 | 205 | @request_token=@consumer.get_request_token 206 | assert @request_token 207 | assert_equal "requestkey",@request_token.token 208 | assert_equal "requestsecret",@request_token.secret 209 | assert_equal "http://term.ie/oauth/example/authorize.php?oauth_token=requestkey",@request_token.authorize_url 210 | 211 | @access_token=@request_token.get_access_token 212 | assert @access_token 213 | assert_equal "accesskey",@access_token.token 214 | assert_equal "accesssecret",@access_token.secret 215 | 216 | @response=@access_token.get("/oauth/example/echo_api.php?ok=hello&test=this") 217 | assert @response 218 | assert_equal "200",@response.code 219 | assert_equal( "ok=hello&test=this",@response.body) 220 | 221 | @response=@access_token.post("/oauth/example/echo_api.php",{'ok'=>'hello','test'=>'this'}) 222 | assert @response 223 | assert_equal "200",@response.code 224 | assert_equal( "ok=hello&test=this",@response.body) 225 | end 226 | 227 | 228 | # This test does an actual https request (the result doesn't matter) 229 | # to initialize the same way as get_request_token does. Can be any 230 | # site that supports https. 231 | # 232 | # It also generates "warning: using default DH parameters." which I 233 | # don't know how to get rid of 234 | # def test_serialization_with_https 235 | # consumer = OAuth::Consumer.new('token', 'secret', :site => 'https://plazes.net') 236 | # consumer.http.verify_mode = OpenSSL::SSL::VERIFY_NONE 237 | # consumer.http.get('/') 238 | # 239 | # assert_nothing_raised do 240 | # # Specifically this should not raise TypeError: no marshal_dump 241 | # # is defined for class OpenSSL::SSL::SSLContext 242 | # Marshal.dump(consumer) 243 | # end 244 | # end 245 | # 246 | def test_get_request_token_with_custom_arguments 247 | stub_test_ie 248 | 249 | @consumer=OAuth::Consumer.new( 250 | "key", 251 | "secret", 252 | { 253 | :site=>"http://term.ie", 254 | :request_token_path=>"/oauth/example/request_token.php", 255 | :access_token_path=>"/oauth/example/access_token.php", 256 | :authorize_path=>"/oauth/example/authorize.php" 257 | }) 258 | 259 | @consumer.get_request_token({}, {:scope => "http://www.google.com/calendar/feeds http://picasaweb.google.com/data"}) 260 | 261 | # Because this is a POST request, create_http_request should take the first element of *arguments 262 | # and turn it into URL-encoded data in the body of the POST. 263 | end 264 | 265 | def test_post_with_body_stream 266 | stub_test_ie 267 | 268 | @consumer=OAuth::Consumer.new( 269 | "key", 270 | "secret", 271 | { 272 | :site=>"http://term.ie", 273 | :request_token_path=>"/oauth/example/request_token.php", 274 | :access_token_path=>"/oauth/example/access_token.php", 275 | :authorize_path=>"/oauth/example/authorize.php" 276 | }) 277 | 278 | 279 | @request_token=@consumer.get_request_token 280 | @access_token=@request_token.get_access_token 281 | 282 | request_body_string = "Hello, hello, hello" 283 | request_body_stream = StringIO.new( request_body_string ) 284 | 285 | @response=@access_token.post("/oauth/example/echo_api.php",request_body_stream) 286 | assert @response 287 | assert_equal "200",@response.code 288 | 289 | request_body_file = File.open(__FILE__) 290 | 291 | @response=@access_token.post("/oauth/example/echo_api.php",request_body_file) 292 | assert @response 293 | assert_equal "200",@response.code 294 | 295 | # unfortunately I don't know of a way to test that the body data was received correctly since the test server at http://term.ie 296 | # echos back any non-oauth parameters but not the body. However, this does test that the request is still correctly signed 297 | # (including the Content-Length header) and that the server received Content-Length bytes of body since it won't process the 298 | # request & respond until the full body length is received. 299 | end 300 | 301 | private 302 | 303 | def request_parameters_to_s 304 | @request_parameters.map { |k,v| "#{k}=#{v}" }.join("&") 305 | end 306 | end 307 | end 308 | --------------------------------------------------------------------------------