require 'openssl' require 'rest-core/event' require 'rest-core/util/hmac' module RestCore # http://tools.ietf.org/html/rfc5849 class Oauth1Header def self.members [:request_token_path, :access_token_path, :authorize_path, :consumer_key, :consumer_secret, :oauth_callback, :oauth_verifier, :oauth_token, :oauth_token_secret, :data] end include Middleware def call env, &k start_time = Time.now headers = {'Authorization' => oauth_header(env)}. merge(env[REQUEST_HEADERS]) event = Event::WithHeader.new(Time.now - start_time, "Authorization: #{headers['Authorization']}") app.call(log(env.merge(REQUEST_HEADERS => headers), event), &k) end def oauth_header env oauth = attach_signature(env, 'oauth_consumer_key' => consumer_key(env), 'oauth_signature_method' => 'HMAC-SHA1', 'oauth_timestamp' => Time.now.to_i.to_s, 'oauth_nonce' => nonce, 'oauth_version' => '1.0', 'oauth_callback' => oauth_callback(env), 'oauth_verifier' => oauth_verifier(env), 'oauth_token' => oauth_token(env)). map{ |(k, v)| "#{k}=\"#{escape(v)}\"" }.join(', ') "OAuth #{oauth}" end def attach_signature env, oauth_params params = reject_blank(oauth_params) params.merge('oauth_signature' => signature(env, params)) end def signature env, params [Hmac.sha1("#{consumer_secret(env)}&#{oauth_token_secret(env)}", base_string(env, params))].pack('m0') end def base_string env, oauth_params method = env[REQUEST_METHOD].to_s.upcase base_uri = env[REQUEST_PATH] payload = payload_params(env) query = reject_blank(env[REQUEST_QUERY]) params = reject_blank(oauth_params.merge(query.merge(payload))). to_a.sort.map{ |(k, v)| "#{escape(k.to_s)}=#{escape(v.to_s)}"}.join('&') "#{method}&#{escape(base_uri)}&#{escape(params)}" end def nonce [OpenSSL::Random.random_bytes(32)].pack('m0').tr("+/=", '') end # according to OAuth 1.0a spec, only: # Content-Type: application/x-www-form-urlencoded # should take payload as a part of the base_string def payload_params env # if we already specified Content-Type and which is not # application/x-www-form-urlencoded, then we should not # take payload as a part of the base_string if env[REQUEST_HEADERS].kind_of?(Hash) && env[REQUEST_HEADERS]['Content-Type'] && env[REQUEST_HEADERS]['Content-Type'] != 'application/x-www-form-urlencoded' {} # if it contains any binary data, # then it shouldn't be application/x-www-form-urlencoded either # the Content-Type header would be handled in our HTTP client elsif contain_binary?(env[REQUEST_PAYLOAD]) {} # so the Content-Type header must be application/x-www-form-urlencoded else reject_blank(env[REQUEST_PAYLOAD]) end end def reject_blank params params.reject{ |k, v| v.nil? || v == false || (v.respond_to?(:strip) && v.respond_to?(:empty) && v.strip.empty? == true) } end end end