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

Attributes

scheme[R]

Authentication scheme.

Public Class Methods

new() click to toggle source

Creates new DigestAuth filter.

Calls superclass method
# File lib/httpclient/auth.rb, line 802
def initialize
  super
  @config = nil # common config
  @auth = {} # configs for each site
  @challenge = {}
  @nonce_count = 0
  @signature_handler = {
    'HMAC-SHA1' => method(:sign_hmac_sha1)
  }
  @scheme = "OAuth"
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 875
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 797
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 856
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 846
def get_config(uri = nil)
  synchronize {
    do_get_config(uri)
  }
end
reset_challenge() click to toggle source

Resets challenge state. Do not send '*Authorization' header until the server sends '*Authentication' again.

# File lib/httpclient/auth.rb, line 816
def reset_challenge
  synchronize do
    @challenge.clear
  end
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 824
def set(*args)
  # not supported
end
set?() click to toggle source

have we marked this as set - ie that it's valid to use in this context?

# File lib/httpclient/auth.rb, line 829
def set?
  true
end
set_config(uri, config) click to toggle source

Set authentication credential.

# File lib/httpclient/auth.rb, line 834
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 899
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 948
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 888
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 927
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 931
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 920
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 939
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 972
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