class HTTPClient::OAuth
CAUTION: This impl does NOT support OAuth Request Body Hash spec for now.
yourself.
Core 1.0 spec for now. You need to obtain Access token and Access secret by
CAUTION: This impl only support ‘#7 Accessing Protected Resources’ in OAuth
Used in WWWAuth.
Authentication filter for handling OAuth negotiation.
def self.escape(str) # :nodoc:
def self.escape(str) # :nodoc: if str.respond_to?(:force_encoding) str.dup.force_encoding('BINARY').gsub(/([^a-zA-Z0-9_.~-]+)/) { '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase } else str.gsub(/([^a-zA-Z0-9_.~-]+)/n) { '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase } end end
def calc_cred(req, config)
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
def challenge(uri, param_str = nil)
Normally OAuthClient handles this correctly but see how it uses when
method just remember URL (nil means 'any') for the next connection.
retry which should not work in OAuth authentication context. This
challenge() in OAuth handler always returns false to avoid connection
Challenge handler: remember URL for response.
def challenge(uri, param_str = nil) synchronize { if uri.nil? @challenge[nil] = true else @challenge[urify(uri)] = true end false } end
def create_base_string(config, header, req)
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
def do_get_config(uri = nil)
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
def encode_header(k, v)
def encode_header(k, v) %Q(#{escape(k.to_s)}="#{escape(v.to_s)}") end
def encode_param(params)
def encode_param(params) params.map { |k, v| [v].flatten.map { |vv| %Q(#{escape(k.to_s)}=#{escape(vv.to_s)}) } }.flatten end
def escape(str)
def escape(str) self.class.escape(str) end
def generate_nonce
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
def get(req)
* child page of challengeable(got *Authenticate before) uri and,
It sends cred only when a given uri is;
Response handler: returns credential.
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
def get_config(uri = nil)
def get_config(uri = nil) synchronize { do_get_config(uri) } end
def initialize
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
def set(*args)
Set authentication credential.
def set(*args) # not supported end
def set?
def set? !@challenge.empty? end
def set_config(uri, config)
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
def sign(config, header, req)
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
def sign_hmac_sha1(config, base_string)
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