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_token'] = config.token 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_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)
def challenge(uri, param_str = nil) if uri.nil? @challengeable[nil] = true else @challengeable[urify(uri)] = true end true 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 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 return nil unless @challengeable[nil] or @challengeable.find { |uri, ok| Util.uri_part_of(target_uri, uri) and ok } config = get_config(target_uri) || @config return nil unless config calc_cred(req, config) end
def get_config(uri = nil)
def 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 initialize
def initialize @config = nil # common config @auth = {} # configs for each site @challengeable = {} @nonce_count = 0 @signature_handler = { 'HMAC-SHA1' => method(:sign_hmac_sha1) } @scheme = "OAuth" end
def reset_challenge
Resets challenge state. Do not send '*Authorization' header until the
def reset_challenge @challengeable.clear end
def set(*args)
Set authentication credential.
def set(*args) # not supported end
def set?
def set? true end
def set_config(uri, config)
def set_config(uri, config) if uri.nil? @config = config else uri = Util.uri_dirname(urify(uri)) @auth[uri] = config 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