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