From 32c91789f92fcf142ea6b0c37c9ebc13c83f66cf Mon Sep 17 00:00:00 2001 From: Matt Jones Date: Fri, 3 Feb 2012 17:57:24 -0600 Subject: [PATCH] have OAuth correctly handle non-urlencoded request bodies --- lib/faraday_middleware/request/oauth.rb | 34 +++++++++++++--- spec/oauth_spec.rb | 53 ++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/lib/faraday_middleware/request/oauth.rb b/lib/faraday_middleware/request/oauth.rb index 16673d92..4054c889 100644 --- a/lib/faraday_middleware/request/oauth.rb +++ b/lib/faraday_middleware/request/oauth.rb @@ -11,10 +11,19 @@ module FaradayMiddleware # The signature is added to the "Authorization" HTTP request header. If the # value for this header already exists, it is not overriden. # - # For requests that have parameters in the body, such as POST, this - # middleware expects them to be in Hash form, i.e. not encoded to string. - # This means this middleware has to be positioned on the stack before any - # encoding middleware such as UrlEncoded. + # If no Content-Type header is specified, this middleware assumes that the + # body hash should be included in the signature parameters. Otherwise, it only + # includes them if the Content-Type is application/x-www-form-urlencoded, as + # required by the OAuth 1.0 specification. + # + # If the body is a string and the Content-Type is either unspecified or + # application/x-www-form-urlencoded, this middleware re-parses the body + # using Faraday::Utils.parse_nested_query and includes the result in the + # OAuth signature parameters. Thus, this middleware can be included either + # before *or* after UrlEncoded. + # + # All other types of encoding middleware should appear *before* this + # middleware to ensure that Content-Type is set appropriately. class OAuth < Faraday::Middleware dependency 'simple_oauth' @@ -50,7 +59,22 @@ def oauth_options(env) end def body_params(env) - env[:body] || {} + if include_body_params?(env) + if env[:body].respond_to?(:to_str) + # same test Faraday::Request::UrlEncoded uses to check for String + # this should exactly reverse that middleware's work + Faraday::Utils.parse_nested_query(env[:body]) + else + env[:body] || {} + end + else + {} + end + end + + def include_body_params?(env) + # see RFC 5489, section 3.4.1.3.1 for details + env[:request_headers]['Content-Type'].nil? || env[:request_headers]['Content-Type'] == 'application/x-www-form-urlencoded' end def signature_params(params) diff --git a/spec/oauth_spec.rb b/spec/oauth_spec.rb index 1ff1112b..39f38f5b 100644 --- a/spec/oauth_spec.rb +++ b/spec/oauth_spec.rb @@ -14,11 +14,12 @@ def auth_values(env) end end - def perform(oauth_options = {}, headers = {}) + def perform(oauth_options = {}, headers = {}, params = {}) env = { :url => URI('http://example.com/'), :request_headers => Faraday::Utils::Headers.new.update(headers), - :request => {} + :request => {}, + :body => params } unless oauth_options.is_a? Hash and oauth_options.empty? env[:request][:oauth] = oauth_options @@ -98,4 +99,52 @@ def make_app auth.should_not include('oauth_token') end end + + context "handling body parameters" do + let(:options) { [{ :consumer_key => 'CKEY', + :consumer_secret => 'CSECRET', + :nonce => '547fed103e122eecf84c080843eedfe6', + :timestamp => '1286830180'}] } + + it "does not include the body with a Content-Type that is not application/x-www-form-urlencoded" do + value = { 'foo' => 'bar' } + auth_header_with = auth_header(perform({}, { 'Content-Type' => 'application/json' }, JSON.dump(value))) + auth_header_without = auth_header(perform({}, { 'Content-Type' => 'application/json' }, {})) + + auth_header_with.should == auth_header_without + end + + it "includes the body parameters with Content-Type application/x-www-form-urlencoded" do + value = { 'foo' => 'bar' } + auth_header_with = auth_header(perform({}, { 'Content-Type' => 'application/x-www-form-urlencoded' }, {})) + auth_header_without = auth_header(perform({}, { 'Content-Type' => 'application/x-www-form-urlencoded' }, value)) + + auth_header_with.should_not == auth_header_without + end + + it "includes the body parameters with an unspecified Content-Type" do + value = { 'foo' => 'bar' } + auth_header_with = auth_header(perform({}, {}, value)) + auth_header_without = auth_header(perform({}, { 'Content-Type' => 'application/x-www-form-urlencoded' }, value)) + + auth_header_with.should == auth_header_without + end + + it "includes the body parameters with Content-Type application/x-www-form-urlencoded and a string body" do + value = { 'foo' => 'bar' } + auth_header_hash = auth_header(perform({}, { 'Content-Type' => 'application/x-www-form-urlencoded' }, value)) + auth_header_string = auth_header(perform({}, { 'Content-Type' => 'application/x-www-form-urlencoded' }, Faraday::Utils.build_query(value))) + auth_header_string.should == auth_header_hash + end + + it "includes the body parameters with Content-Type application/x-www-form-urlencoded and a string body with nested params" do + # simulates the behavior of Faraday::MiddleWare::UrlEncoded + value = { 'foo' => ['bar', 'baz', 'wat'] } + auth_header_hash = auth_header(perform({}, { 'Content-Type' => 'application/x-www-form-urlencoded' }, value)) + auth_header_string = auth_header(perform({}, { 'Content-Type' => 'application/x-www-form-urlencoded' }, Faraday::Utils.build_nested_query(value))) + auth_header_string.should == auth_header_hash + end + + end + end