From 17eb01f5a21ba8ba9b2e81f258916e46bd5eb0e6 Mon Sep 17 00:00:00 2001 From: Shohei Maeda Date: Thu, 21 Jan 2021 03:44:08 +0900 Subject: [PATCH] merge master --- .travis.yml | 5 +- HISTORY | 7 ++ examples/twitter.rb | 42 +++++++++ examples/yql.rb | 2 +- lib/oauth/client/helper.rb | 10 +- lib/oauth/consumer.rb | 53 +++++++++-- lib/oauth/tokens/request_token.rb | 13 ++- lib/oauth/version.rb | 2 +- oauth.gemspec | 2 +- test/units/test_client_helper.rb | 147 ++++++++++++++++++++++++++++++ test/units/test_consumer.rb | 61 +++++++++++++ test/units/test_request_token.rb | 34 ++++++- 12 files changed, 354 insertions(+), 24 deletions(-) create mode 100644 examples/twitter.rb create mode 100644 test/units/test_client_helper.rb diff --git a/.travis.yml b/.travis.yml index 4903329f..0f4962c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,13 @@ language: ruby rvm: - "ruby-head" +- "2.7" - "2.4.0" - "2.3" - "2.2" - +matrix: + allow_failures: + - rvm: "ruby-head" addons: code_climate: repo_token: 8f697ca756250f0c2c54170ae27e8a9c459d18a0236903b11291c88291b3aac9 diff --git a/HISTORY b/HISTORY index b4a52581..478c7f2a 100644 --- a/HISTORY +++ b/HISTORY @@ -1,5 +1,12 @@ === CURRENT +=== 0.5.5 2020-01-19 + +* Allow redirect to different host but same path +* Add :allow_empty_params option (#155) +* Fixes ssl-noverify +* Various cleanups + === 0.5.4 2017-12-08 * Fixes UnknownRequestType on Rails 5.1 for ActionDispatch::Request (xprazak2) diff --git a/examples/twitter.rb b/examples/twitter.rb new file mode 100644 index 00000000..e10eb422 --- /dev/null +++ b/examples/twitter.rb @@ -0,0 +1,42 @@ +#!/usr/bin/env ruby -r rubygems +# +# ./twitter.rb --consumer-key --consumer-secret + +require 'oauth' +require 'optparse' +require 'json' +require 'pp' + +options = {} + +option_parser = OptionParser.new do |opts| + opts.banner = "Usage: #{$0} [options] " + + opts.on("--consumer-key KEY", "Specifies the consumer key to use.") do |v| + options[:consumer_key] = v + end + + opts.on("--consumer-secret SECRET", "Specifies the consumer secret to use.") do |v| + options[:consumer_secret] = v + end +end + +option_parser.parse! +query = ARGV.pop +query = STDIN.read if query == "-" + +if options[:consumer_key].nil? || options[:consumer_secret].nil? || query.nil? + puts option_parser.help + exit 1 +end + +consumer = OAuth::Consumer.new \ + options[:consumer_key], + options[:consumer_secret], + :site => "https://api.twitter.com" + +access_token = OAuth::AccessToken.new(consumer) + +response = access_token.request(:get, "/1.1/statuses/show/#{OAuth::Helper.escape(query)}.json") +rsp = JSON.parse(response.body) +pp rsp diff --git a/examples/yql.rb b/examples/yql.rb index 41bb1f21..c9c6e343 100755 --- a/examples/yql.rb +++ b/examples/yql.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby -rubygems +#!/usr/bin/env ruby -r rubygems # Sample queries: # ./yql.rb --consumer-key --consumer-secret "show tables" diff --git a/lib/oauth/client/helper.rb b/lib/oauth/client/helper.rb index 6c8b19b2..5fe2be24 100644 --- a/lib/oauth/client/helper.rb +++ b/lib/oauth/client/helper.rb @@ -27,7 +27,7 @@ def timestamp end def oauth_parameters - { + out = { 'oauth_body_hash' => options[:body_hash], 'oauth_callback' => options[:oauth_callback], 'oauth_consumer_key' => options[:consumer].key, @@ -38,7 +38,13 @@ def oauth_parameters 'oauth_verifier' => options[:oauth_verifier], 'oauth_version' => (options[:oauth_version] || '1.0'), 'oauth_session_handle' => options[:oauth_session_handle] - }.reject { |k,v| v.to_s == "" } + } + allowed_empty_params = options[:allow_empty_params] + if allowed_empty_params != true && !allowed_empty_params.kind_of?(Array) + allowed_empty_params = allowed_empty_params == false ? [] : [allowed_empty_params] + end + out.select! { |k,v| v.to_s != '' || allowed_empty_params == true || allowed_empty_params.include?(k) } + out end def signature(extra_options = {}) diff --git a/lib/oauth/consumer.rb b/lib/oauth/consumer.rb index 7b2caffd..fd2f3185 100644 --- a/lib/oauth/consumer.rb +++ b/lib/oauth/consumer.rb @@ -8,11 +8,21 @@ module OAuth class Consumer # determine the certificate authority path to verify SSL certs - CA_FILES = %W(#{ENV['SSL_CERT_FILE']} /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt /usr/share/curl/curl-ca-bundle.crt) - CA_FILES.each do |ca_file| - if File.exist?(ca_file) - CA_FILE = ca_file - break + if ENV['SSL_CERT_FILE'] + if File.exist?(ENV['SSL_CERT_FILE']) + CA_FILE = ENV['SSL_CERT_FILE'] + else + raise "The SSL CERT provided does not exist." + end + end + + if !defined?(CA_FILE) + CA_FILES = %W(/etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt /usr/share/curl/curl-ca-bundle.crt) + CA_FILES.each do |ca_file| + if File.exist?(ca_file) + CA_FILE = ca_file + break + end end end CA_FILE = nil unless defined?(CA_FILE) @@ -23,6 +33,7 @@ class Consumer # default paths on site. These are the same as the defaults set up by the generators :request_token_path => '/oauth/request_token', + :authenticate_path => '/oauth/authenticate', :authorize_path => '/oauth/authorize', :access_token_path => '/oauth/access_token', @@ -230,7 +241,14 @@ def token_request(http_method, path, token = nil, request_options = {}, *argumen when (300..399) # this is a redirect uri = URI.parse(response['location']) - response.error! if uri.path == path # careful of those infinite redirects + our_uri = URI.parse(site) + + if uri.path == path && our_uri.host != uri.host + options[:site] = "#{uri.scheme}://#{uri.host}" + @http = create_http + end + + response.error! if uri.path == path && our_uri.host == uri.host # careful of those infinite redirects self.token_request(http_method, uri.path, token, request_options, arguments) when (400..499) raise OAuth::Unauthorized, response @@ -266,6 +284,10 @@ def request_token_path @options[:request_token_path] end + def authenticate_path + @options[:authenticate_path] + end + def authorize_path @options[:authorize_path] end @@ -283,6 +305,14 @@ def request_token_url? @options.has_key?(:request_token_url) end + def authenticate_url + @options[:authenticate_url] || site + authenticate_path + end + + def authenticate_url? + @options.has_key?(:authenticate_url) + end + def authorize_url @options[:authorize_url] || site + authorize_path end @@ -330,12 +360,15 @@ def create_http(_url = nil) http_object.use_ssl = (our_uri.scheme == 'https') - if @options[:ca_file] || CA_FILE - http_object.ca_file = @options[:ca_file] || CA_FILE + if @options[:no_verify] + http_object.verify_mode = OpenSSL::SSL::VERIFY_NONE + else + ca_file = @options[:ca_file] || CA_FILE + if ca_file + http_object.ca_file = ca_file + end http_object.verify_mode = OpenSSL::SSL::VERIFY_PEER http_object.verify_depth = 5 - else - http_object.verify_mode = OpenSSL::SSL::VERIFY_NONE end http_object.read_timeout = http_object.open_timeout = @options[:timeout] || 60 diff --git a/lib/oauth/tokens/request_token.rb b/lib/oauth/tokens/request_token.rb index 39187cd9..6434b95e 100644 --- a/lib/oauth/tokens/request_token.rb +++ b/lib/oauth/tokens/request_token.rb @@ -8,7 +8,14 @@ def authorize_url(params = nil) return nil if self.token.nil? params = (params || {}).merge(:oauth_token => self.token) - build_authorize_url(consumer.authorize_url, params) + build_url(consumer.authorize_url, params) + end + + def authenticate_url(params = nil) + return nil if self.token.nil? + + params = (params || {}).merge(:oauth_token => self.token) + build_url(consumer.authenticate_url, params) end def callback_confirmed? @@ -23,8 +30,8 @@ def get_access_token(options = {}, *arguments) protected - # construct an authorization url - def build_authorize_url(base_url, params) + # construct an authorization or authentication url + def build_url(base_url, params) uri = URI.parse(base_url.to_s) queries = {} queries = Hash[URI.decode_www_form(uri.query)] if uri.query diff --git a/lib/oauth/version.rb b/lib/oauth/version.rb index 5fdd439d..3ce42460 100644 --- a/lib/oauth/version.rb +++ b/lib/oauth/version.rb @@ -1,3 +1,3 @@ module OAuth - VERSION = "0.5.4" + VERSION = "0.5.5" end diff --git a/oauth.gemspec b/oauth.gemspec index 0877588c..1ae285a2 100644 --- a/oauth.gemspec +++ b/oauth.gemspec @@ -32,7 +32,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency("iconv") spec.add_development_dependency("rack", "~> 2.0") spec.add_development_dependency("rack-test") - spec.add_development_dependency("mocha", ">= 0.9.12") + spec.add_development_dependency("mocha", ">= 0.9.12", "<=1.1.0") spec.add_development_dependency("typhoeus", ">= 0.1.13") spec.add_development_dependency("em-http-request", "0.2.11") spec.add_development_dependency("curb") diff --git a/test/units/test_client_helper.rb b/test/units/test_client_helper.rb new file mode 100644 index 00000000..d6ed973a --- /dev/null +++ b/test/units/test_client_helper.rb @@ -0,0 +1,147 @@ +require File.expand_path('../../test_helper', __FILE__) + +require 'oauth/client' + +class ClientHelperTest < Minitest::Test + + def setup + @consumer=OAuth::Consumer.new( + 'consumer_key_86cad9', '5888bf0345e5d237', + { + :site=>"http://blabla.bla", + :proxy=>"http://user:password@proxy.bla:8080", + :request_token_path=>"/oauth/example/request_token.php", + :access_token_path=>"/oauth/example/access_token.php", + :authorize_path=>"/oauth/example/authorize.php", + :scheme=>:header, + :http_method=>:get + }) + end + + def test_oauth_parameters_allow_empty_params_default + helper = OAuth::Client::Helper.new(nil, { + :consumer => @consumer + }) + helper.stub :timestamp, '0' do + helper.stub :nonce, 'nonce' do + expected = { + "oauth_consumer_key"=>"consumer_key_86cad9", + "oauth_signature_method"=>"HMAC-SHA1", + "oauth_timestamp"=>"0", + "oauth_nonce"=>"nonce", + "oauth_version"=>"1.0" + } + assert_equal expected, helper.oauth_parameters + end + end + end + + def test_oauth_parameters_allow_empty_params_true + input = true + helper = OAuth::Client::Helper.new(nil, { + :consumer => @consumer, + :allow_empty_params => input + }) + helper.stub :timestamp, '0' do + helper.stub :nonce, 'nonce' do + expected = { + "oauth_body_hash"=>nil, + "oauth_callback"=>nil, + "oauth_consumer_key"=>"consumer_key_86cad9", + "oauth_token"=>"", + "oauth_signature_method"=>"HMAC-SHA1", + "oauth_timestamp"=>"0", + "oauth_nonce"=>"nonce", + "oauth_verifier"=>nil, + "oauth_version"=>"1.0", + "oauth_session_handle"=>nil + } + assert_equal expected, helper.oauth_parameters + end + end + end + + def test_oauth_parameters_allow_empty_params_false + input = false + helper = OAuth::Client::Helper.new(nil, { + :consumer => @consumer, + :allow_empty_params => input + }) + helper.stub :timestamp, '0' do + helper.stub :nonce, 'nonce' do + expected = { + "oauth_consumer_key"=>"consumer_key_86cad9", + "oauth_signature_method"=>"HMAC-SHA1", + "oauth_timestamp"=>"0", + "oauth_nonce"=>"nonce", + "oauth_version"=>"1.0" + } + assert_equal expected, helper.oauth_parameters + end + end + end + + def test_oauth_parameters_allow_empty_params_only_oauth_token_as_string + input = 'oauth_token' + helper = OAuth::Client::Helper.new(nil, { + :consumer => @consumer, + :allow_empty_params => input + }) + helper.stub :timestamp, '0' do + helper.stub :nonce, 'nonce' do + expected = { + "oauth_consumer_key"=>"consumer_key_86cad9", + "oauth_token"=>"", + "oauth_signature_method"=>"HMAC-SHA1", + "oauth_timestamp"=>"0", + "oauth_nonce"=>"nonce", + "oauth_version"=>"1.0", + } + assert_equal expected, helper.oauth_parameters + end + end + end + + def test_oauth_parameters_allow_empty_params_only_oauth_token_as_array + input = ['oauth_token'] + helper = OAuth::Client::Helper.new(nil, { + :consumer => @consumer, + :allow_empty_params => input + }) + helper.stub :timestamp, '0' do + helper.stub :nonce, 'nonce' do + expected = { + "oauth_consumer_key"=>"consumer_key_86cad9", + "oauth_token"=>"", + "oauth_signature_method"=>"HMAC-SHA1", + "oauth_timestamp"=>"0", + "oauth_nonce"=>"nonce", + "oauth_version"=>"1.0", + } + assert_equal expected, helper.oauth_parameters + end + end + end + + def test_oauth_parameters_allow_empty_params_oauth_token_and_oauth_session_handle + input = ['oauth_token', 'oauth_session_handle'] + helper = OAuth::Client::Helper.new(nil, { + :consumer => @consumer, + :allow_empty_params => input + }) + helper.stub :timestamp, '0' do + helper.stub :nonce, 'nonce' do + expected = { + "oauth_consumer_key"=>"consumer_key_86cad9", + "oauth_token"=>"", + "oauth_signature_method"=>"HMAC-SHA1", + "oauth_timestamp"=>"0", + "oauth_nonce"=>"nonce", + "oauth_version"=>"1.0", + "oauth_session_handle"=>nil + } + assert_equal expected, helper.oauth_parameters + end + end + end +end diff --git a/test/units/test_consumer.rb b/test/units/test_consumer.rb index af97bd13..8c58674f 100644 --- a/test/units/test_consumer.rb +++ b/test/units/test_consumer.rb @@ -165,6 +165,54 @@ def test_getting_tokens_doesnt_add_paths_if_full_url_is_specified @consumer.get_request_token end + def test_noverify_true + @consumer = OAuth::Consumer.new( + "key", + "secret", + { + :site => "https://api.mysite.co.nz/v1", + :request_token_url => "https://authentication.mysite.co.nz/Oauth/RequestToken", + :no_verify => true + }) + + stub_request(:post, "https://authentication.mysite.co.nz/Oauth/RequestToken").to_return(:body => "success", :status => 200) + + Net::HTTP.any_instance.expects(:'verify_mode=').with(OpenSSL::SSL::VERIFY_NONE) + + @consumer.get_request_token + end + + def test_noverify_false + @consumer = OAuth::Consumer.new( + "key", + "secret", + { + :site => "https://api.mysite.co.nz/v1", + :request_token_url => "https://authentication.mysite.co.nz/Oauth/RequestToken", + :no_verify => false + }) + + stub_request(:post, "https://authentication.mysite.co.nz/Oauth/RequestToken").to_return(:body => "success", :status => 200) + + Net::HTTP.any_instance.expects(:'verify_mode=').with(OpenSSL::SSL::VERIFY_PEER) + @consumer.get_request_token + end + + def test_noverify_empty + @consumer = OAuth::Consumer.new( + "key", + "secret", + { + :site => "https://api.mysite.co.nz/v1", + :request_token_url => "https://authentication.mysite.co.nz/Oauth/RequestToken" + }) + + stub_request(:post, "https://authentication.mysite.co.nz/Oauth/RequestToken").to_return(:body => "success", :status => 200) + + Net::HTTP.any_instance.expects(:'verify_mode=').with(OpenSSL::SSL::VERIFY_PEER) + @consumer.get_request_token + end + def test_token_request_identifies_itself_as_a_token_request request_options = {} @consumer.stubs(:request).returns(create_stub_http_response) @@ -202,6 +250,19 @@ def test_token_request_follows_redirect assert_equal 'secret', hash[:oauth_token_secret] end + def test_follow_redirect_different_host_same_path + request_uri = URI.parse("https://example.com/request_token") + redirect_uri = URI.parse("https://foobar.com/request_token") + + stub_request(:get, "http://example.com/request_token").to_return(:status => 301, :headers => {'Location' => redirect_uri.to_s}) + stub_request(:get, "https://foobar.com/request_token").to_return(:body => "oauth_token=token&oauth_token_secret=secret") + + hash = @consumer.token_request(:get, request_uri.path) {{ :oauth_token => 'token', :oauth_token_secret => 'secret' }} + + assert_equal 'token', hash[:oauth_token] + assert_equal 'secret', hash[:oauth_token_secret] + end + def test_that_can_provide_a_block_to_interpret_a_request_token_response @consumer.expects(:request).returns(create_stub_http_response) diff --git a/test/units/test_request_token.rb b/test/units/test_request_token.rb index db52a2fd..850f28e4 100644 --- a/test/units/test_request_token.rb +++ b/test/units/test_request_token.rb @@ -1,8 +1,8 @@ require File.expand_path('../../test_helper', __FILE__) class StubbedToken < OAuth::RequestToken - define_method :build_authorize_url_promoted do |root_domain, params| - build_authorize_url root_domain, params + define_method :build_url_promoted do |root_domain, params| + build_url root_domain, params end end @@ -40,14 +40,38 @@ def test_request_token_returns_nil_authorize_url_when_token_is_nil assert_nil @request_token.authorize_url end + def test_request_token_builds_authenticate_url_connectly_with_additional_params + authenticate_url = @request_token.authenticate_url({:oauth_callback => "github.com"}) + assert authenticate_url + assert_match(/oauth_token/, authenticate_url) + assert_match(/oauth_callback/, authenticate_url) + end + + def test_request_token_builds_authenticate_url_connectly_with_no_or_nil_params + # we should only have 1 key in the url returned if we didn't pass anything. + # this is the only required param to authenticate the client. + authenticate_url = @request_token.authenticate_url(nil) + assert authenticate_url + assert_match(/\?oauth_token=/, authenticate_url) + + authenticate_url2 = @request_token.authenticate_url + assert authenticate_url2 + assert_match(/\?oauth_token=/, authenticate_url2) + end + + def test_request_token_returns_nil_authenticate_url_when_token_is_nil + @request_token.token = nil + assert_nil @request_token.authenticate_url + end + #TODO: mock out the Consumer to test the Consumer/AccessToken interaction. def test_get_access_token end - def test_build_authorize_url + def test_build_url @stubbed_token = StubbedToken.new(nil, nil, nil) - assert_respond_to @stubbed_token, :build_authorize_url_promoted - url = @stubbed_token.build_authorize_url_promoted( + assert_respond_to @stubbed_token, :build_url_promoted + url = @stubbed_token.build_url_promoted( "http://github.com/oauth/authorize", {:foo => "bar bar"}) assert url