class HTTPClient::OAuth
Authentication filter for handling OAuth
negotiation. Used in WWWAuth
.
CAUTION: This impl only support '#7 Accessing Protected Resources' in OAuth
Core 1.0 spec for now. You need to obtain Access token and Access secret by yourself.
CAUTION: This impl does NOT support OAuth
Request Body Hash spec for now. oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
Public Class Methods
Creates new DigestAuth
filter.
HTTPClient::AuthBase::new
# File lib/httpclient/auth.rb, line 753 def initialize super('OAuth') @config = nil # common config @auth = {} # configs for each site @nonce_count = 0 @signature_handler = { 'HMAC-SHA1' => method(:sign_hmac_sha1) } end
Public Instance Methods
Challenge handler: remember URL for response.
challenge() in OAuth
handler always returns false to avoid connection retry which should not work in OAuth
authentication context. This method just remember URL (nil means 'any') for the next connection. Normally OAuthClient
handles this correctly but see how it uses when you need to use this class directly.
# File lib/httpclient/auth.rb, line 816 def challenge(uri, param_str = nil) synchronize { if uri.nil? @challenge[nil] = true else @challenge[urify(uri)] = true end false } end
# File lib/httpclient/auth.rb, line 748 def escape(str) self.class.escape(str) end
Response handler: returns credential. It sends cred only when a given uri is;
-
child page of challengeable(got *Authenticate before) uri and,
-
child page of defined credential
# File lib/httpclient/auth.rb, line 797 def get(req) target_uri = req.header.request_uri synchronize { return nil unless @challenge[nil] or @challenge.find { |uri, ok| Util.uri_part_of(target_uri, uri) and ok } config = do_get_config(target_uri) || @config return nil unless config calc_cred(req, config) } end
Get authentication credential.
# File lib/httpclient/auth.rb, line 787 def get_config(uri = nil) synchronize { do_get_config(uri) } end
Set authentication credential. You cannot set OAuth
config via WWWAuth#set_auth
. Use OAuth#config=
# File lib/httpclient/auth.rb, line 765 def set(*args) # not supported end
Check always (not effective but it works)
# File lib/httpclient/auth.rb, line 770 def set? !@challenge.empty? end
Set authentication credential.
# File lib/httpclient/auth.rb, line 775 def set_config(uri, config) synchronize do if uri.nil? @config = config else uri = Util.uri_dirname(urify(uri)) @auth[uri] = config end end end
Private Instance Methods
# File lib/httpclient/auth.rb, line 840 def calc_cred(req, config) header = {} header['oauth_consumer_key'] = config.consumer_key header['oauth_signature_method'] = config.signature_method header['oauth_timestamp'] = config.debug_timestamp || Time.now.to_i.to_s header['oauth_nonce'] = config.debug_nonce || generate_nonce() header['oauth_token'] = config.token if config.token header['oauth_version'] = config.version if config.version header['oauth_callback'] = config.callback if config.callback header['oauth_verifier'] = config.verifier if config.verifier header['oauth_session_handle'] = config.session_handle if config.session_handle signature = sign(config, header, req) header['oauth_signature'] = signature # no need to do but we should sort for easier to test. str = header.sort_by { |k, v| k }.map { |k, v| encode_header(k, v) }.join(', ') if config.realm str = %Q(realm="#{config.realm}", ) + str end str end
# File lib/httpclient/auth.rb, line 889 def create_base_string(config, header, req) params = encode_param(header) query = req.header.request_query if query and HTTP::Message.multiparam_query?(query) params += encode_param(query) end # captures HTTP Message body only for 'application/x-www-form-urlencoded' if req.header.contenttype == 'application/x-www-form-urlencoded' and req.http_body.size params += encode_param(HTTP::Message.parse(req.http_body.content)) end uri = req.header.request_uri if uri.query params += encode_param(HTTP::Message.parse(uri.query)) end if uri.port == uri.default_port request_url = "#{uri.scheme.downcase}://#{uri.host}#{uri.path}" else request_url = "#{uri.scheme.downcase}://#{uri.host}:#{uri.port}#{uri.path}" end [req.header.request_method.upcase, request_url, params.sort.join('&')].map { |e| escape(e) }.join('&') end
# File lib/httpclient/auth.rb, line 829 def do_get_config(uri = nil) if uri.nil? @config else uri = urify(uri) Util.hash_find_value(@auth) { |cand_uri, cred| Util.uri_part_of(uri, cand_uri) } end end
# File lib/httpclient/auth.rb, line 868 def encode_header(k, v) %Q(#{escape(k.to_s)}="#{escape(v.to_s)}") end
# File lib/httpclient/auth.rb, line 872 def encode_param(params) params.map { |k, v| [v].flatten.map { |vv| %Q(#{escape(k.to_s)}=#{escape(vv.to_s)}) } }.flatten end
# File lib/httpclient/auth.rb, line 861 def generate_nonce @nonce_count += 1 now = "%012d" % Time.now.to_i pk = Digest::MD5.hexdigest([@nonce_count.to_s, now, self.__id__, Process.pid, rand(65535)].join)[0, 32] [now + ':' + pk].pack('m*').chop end
# File lib/httpclient/auth.rb, line 880 def sign(config, header, req) base_string = create_base_string(config, header, req) if handler = config.signature_handler[config.signature_method] || @signature_handler[config.signature_method.to_s] handler.call(config, base_string) else raise ConfigurationError.new("Unknown OAuth signature method: #{config.signature_method}") end end
# File lib/httpclient/auth.rb, line 913 def sign_hmac_sha1(config, base_string) unless SSLEnabled raise ConfigurationError.new("openssl required for OAuth implementation") end key = [escape(config.consumer_secret.to_s), escape(config.secret.to_s)].join('&') digester = OpenSSL::Digest::SHA1.new [OpenSSL::HMAC.digest(digester, key, base_string)].pack('m*').chomp end