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

new() click to toggle source

Creates new DigestAuth filter.

Calls superclass method 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(uri, param_str = nil) click to toggle source

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
escape(str) click to toggle source
# File lib/httpclient/auth.rb, line 748
def escape(str)
  self.class.escape(str)
end
get(req) click to toggle source

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_config(uri = nil) click to toggle source

Get authentication credential.

# File lib/httpclient/auth.rb, line 787
def get_config(uri = nil)
  synchronize {
    do_get_config(uri)
  }
end
set(*args) click to toggle source

Set authentication credential. You cannot set OAuth config via HTTPClient::WWWAuth#set_auth. Use OAuth#config=

# File lib/httpclient/auth.rb, line 765
def set(*args)
  # not supported
end
set?() click to toggle source

Check always (not effective but it works)

# File lib/httpclient/auth.rb, line 770
def set?
  !@challenge.empty?
end
set_config(uri, config) click to toggle source

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

calc_cred(req, config) click to toggle source
# 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
create_base_string(config, header, req) click to toggle source
# 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
do_get_config(uri = nil) click to toggle source
# 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
encode_header(k, v) click to toggle source
# File lib/httpclient/auth.rb, line 868
def encode_header(k, v)
  %Q(#{escape(k.to_s)}="#{escape(v.to_s)}")
end
encode_param(params) click to toggle source
# 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
generate_nonce() click to toggle source
# 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
sign(config, header, req) click to toggle source
# 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
sign_hmac_sha1(config, base_string) click to toggle source
# 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