Skip to content

Commit 3edeaf4

Browse files
author
Michael Reinsch
committed
implemented computation of body hash as per OAuth Request Body Hash 1.0 Draft 4
1 parent 9ee25ed commit 3edeaf4

File tree

10 files changed

+108
-27
lines changed

10 files changed

+108
-27
lines changed

History.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
== 0.3.7
2+
* Added computation of oauth_body_hash as per OAuth Request Body Hash 1.0
3+
Draft 4 (Michael Reinsch)
24
* Better marshalling implementation (Yoan Blanc)
35
* Added optional block to OAuth::Consumer.get_*_token (Neill Pearman)
46
* Exclude `oauth_callback` with :exclude_callback (Neill Pearman)

lib/oauth/client/helper.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def timestamp
2929

3030
def oauth_parameters
3131
{
32+
'oauth_body_hash' => options[:body_hash],
3233
'oauth_callback' => options[:oauth_callback],
3334
'oauth_consumer_key' => options[:consumer].key,
3435
'oauth_token' => options[:token] ? options[:token].token : '',
@@ -55,6 +56,10 @@ def signature_base_string(extra_options = {})
5556
:parameters => oauth_parameters}.merge(extra_options) )
5657
end
5758

59+
def hash_body
60+
@options[:body_hash] = OAuth::Signature.body_hash(@request, :parameters => oauth_parameters)
61+
end
62+
5863
def amend_user_agent_header(headers)
5964
@oauth_ua_string ||= "OAuth gem v#{OAuth::VERSION}"
6065
# Net::HTTP in 1.9 appends Ruby

lib/oauth/client/net_http.rb

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,14 @@ class Net::HTTPRequest
2020
#
2121
# This method also modifies the <tt>User-Agent</tt> header to add the OAuth gem version.
2222
#
23-
# See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1]
23+
# See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1],
24+
# {OAuth Request Body Hash 1.0 Draft 4}[http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/4/spec.html]
2425
def oauth!(http, consumer = nil, token = nil, options = {})
25-
options = { :request_uri => oauth_full_request_uri(http),
26-
:consumer => consumer,
27-
:token => token,
28-
:scheme => 'header',
29-
:signature_method => nil,
30-
:nonce => nil,
31-
:timestamp => nil }.merge(options)
32-
33-
@oauth_helper = OAuth::Client::Helper.new(self, options)
26+
helper_options = oauth_helper_options(http, consumer, token, options)
27+
@oauth_helper = OAuth::Client::Helper.new(self, helper_options)
3428
@oauth_helper.amend_user_agent_header(self)
35-
self.send("set_oauth_#{options[:scheme]}")
29+
@oauth_helper.hash_body if oauth_body_hash_required?
30+
self.send("set_oauth_#{helper_options[:scheme]}")
3631
end
3732

3833
# Create a string suitable for signing for an HTTP request. This process involves parameter
@@ -47,21 +42,27 @@ def oauth!(http, consumer = nil, token = nil, options = {})
4742
# * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
4843
# +signature_method+, +nonce+, +timestamp+)
4944
#
50-
# See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1]
45+
# See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1],
46+
# {OAuth Request Body Hash 1.0 Draft 4}[http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/4/spec.html]
5147
def signature_base_string(http, consumer = nil, token = nil, options = {})
52-
options = { :request_uri => oauth_full_request_uri(http),
53-
:consumer => consumer,
54-
:token => token,
55-
:scheme => 'header',
56-
:signature_method => nil,
57-
:nonce => nil,
58-
:timestamp => nil }.merge(options)
59-
60-
OAuth::Client::Helper.new(self, options).signature_base_string
48+
helper_options = oauth_helper_options(http, consumer, token, options)
49+
oauth_helper = OAuth::Client::Helper.new(self, helper_options)
50+
oauth_helper.hash_body if oauth_body_hash_required?
51+
oauth_helper.signature_base_string
6152
end
6253

6354
private
6455

56+
def oauth_helper_options(http, consumer, token, options)
57+
{ :request_uri => oauth_full_request_uri(http),
58+
:consumer => consumer,
59+
:token => token,
60+
:scheme => 'header',
61+
:signature_method => nil,
62+
:nonce => nil,
63+
:timestamp => nil }.merge(options)
64+
end
65+
6566
def oauth_full_request_uri(http)
6667
uri = URI.parse(self.path)
6768
uri.host = http.address
@@ -76,6 +77,10 @@ def oauth_full_request_uri(http)
7677
uri.to_s
7778
end
7879

80+
def oauth_body_hash_required?
81+
request_body_permitted? && content_type != "application/x-www-form-urlencoded"
82+
end
83+
7984
def set_oauth_header
8085
self['Authorization'] = @oauth_helper.header
8186
end

lib/oauth/oauth.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ module OAuth
44
OUT_OF_BAND = "oob"
55

66
# required parameters, per sections 6.1.1, 6.3.1, and 7
7-
PARAMETERS = %w(oauth_callback oauth_consumer_key oauth_token oauth_signature_method oauth_timestamp oauth_nonce oauth_verifier oauth_version oauth_signature)
7+
PARAMETERS = %w(oauth_callback oauth_consumer_key oauth_token
8+
oauth_signature_method oauth_timestamp oauth_nonce oauth_verifier
9+
oauth_version oauth_signature oauth_body_hash)
810

911
# reserved character regexp, per section 5.1
1012
RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~]/

lib/oauth/request_proxy/net_http.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ def parameters
2424
end
2525
end
2626

27+
def body
28+
request.body
29+
end
30+
2731
private
2832

2933
def all_parameters

lib/oauth/signature.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ def self.signature_base_string(request, options = {}, &block)
3535
self.build(request, options, &block).signature_base_string
3636
end
3737

38+
# Create the body hash for a request
39+
def self.body_hash(request, options = {}, &block)
40+
self.build(request, options, &block).body_hash
41+
end
42+
3843
class UnknownSignatureMethod < Exception; end
3944
end
4045
end

lib/oauth/signature/base.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ def self.digest_klass(digest_klass = nil)
2626
@digest_klass = digest_klass
2727
end
2828

29+
def self.hash_class(hash_class = nil)
30+
return @hash_class if hash_class.nil?
31+
@hash_class = hash_class
32+
end
33+
2934
def initialize(request, options = {}, &block)
3035
raise TypeError unless request.kind_of?(OAuth::RequestProxy::Base)
3136
@request = request
@@ -72,6 +77,14 @@ def signature_base_string
7277
request.signature_base_string
7378
end
7479

80+
def body_hash
81+
if self.class.hash_class
82+
Base64.encode64(self.class.hash_class.digest(request.body || '')).chomp.gsub(/\n/,'')
83+
else
84+
nil # no body hash algorithm defined, so don't generate one
85+
end
86+
end
87+
7588
private
7689

7790
def token

lib/oauth/signature/hmac/sha1.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ module OAuth::Signature::HMAC
44
class SHA1 < Base
55
implements 'hmac-sha1'
66
digest_klass 'SHA1'
7+
hash_class ::Digest::SHA1
78
end
89
end

lib/oauth/signature/rsa/sha1.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module OAuth::Signature::RSA
55
class SHA1 < OAuth::Signature::Base
66
implements 'rsa-sha1'
7+
hash_class ::Digest::SHA1
78

89
def ==(cmp_signature)
910
public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(cmp_signature.is_a?(Array) ? cmp_signature.first : cmp_signature), signature_base_string)

test/test_net_http_client.rb

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,32 @@ def test_that_using_auth_headers_on_post_requests_works
6262
assert_matching_headers correct_sorted_params, request['authorization']
6363
end
6464

65+
def test_that_using_auth_headers_on_post_requests_with_data_works
66+
request = Net::HTTP::Post.new(@request_uri.path)
67+
request.body = "data"
68+
request.content_type = 'text/ascii'
69+
request.oauth!(@http, @consumer, @token, {:nonce => @nonce, :timestamp => @timestamp})
70+
71+
assert_equal 'POST', request.method
72+
assert_equal '/test', request.path
73+
assert_equal 'data', request.body
74+
assert_equal 'text/ascii', request.content_type
75+
assert_matching_headers "oauth_nonce=\"225579211881198842005988698334675835446\", oauth_body_hash=\"oXyaqmHoChv3HQ2FCvTluqmAC70%3D\", oauth_signature_method=\"HMAC-SHA1\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"0DA6pGTapdHSqC15RZelY5rNLDw%3D\", oauth_version=\"1.0\"", request['authorization']
76+
end
77+
78+
def test_that_body_hash_is_obmitted_when_no_algorithm_is_defined
79+
request = Net::HTTP::Post.new(@request_uri.path)
80+
request.body = "data"
81+
request.content_type = 'text/ascii'
82+
request.oauth!(@http, @consumer, @token, {:nonce => @nonce, :timestamp => @timestamp, :signature_method => 'plaintext'})
83+
84+
assert_equal 'POST', request.method
85+
assert_equal '/test', request.path
86+
assert_equal 'data', request.body
87+
assert_equal 'text/ascii', request.content_type
88+
assert_matching_headers "oauth_nonce=\"225579211881198842005988698334675835446\", oauth_signature_method=\"plaintext\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"5888bf0345e5d237%263196ffd991c8ebdb\", oauth_version=\"1.0\"", request['authorization']
89+
end
90+
6591
def test_that_version_is_added_to_existing_user_agent
6692
request = Net::HTTP::Post.new(@request_uri.path)
6793
request['User-Agent'] = "MyApp"
@@ -127,14 +153,15 @@ def test_that_using_post_params_works_with_plaintext
127153

128154
def test_that_using_post_with_uri_params_works
129155
request = Net::HTTP::Post.new(@request_uri.path + "?" + request_parameters_to_s)
156+
request.set_form_data( {} ) # just to make sure we have a correct mime type and thus no body hash
130157
request.oauth!(@http, @consumer, @token, {:scheme => 'query_string', :nonce => @nonce, :timestamp => @timestamp})
131158

132159
assert_equal 'POST', request.method
133160
uri = URI.parse(request.path)
134161
assert_equal '/test', uri.path
135162
assert_equal nil, uri.fragment
136163
assert_equal "key=value&oauth_consumer_key=consumer_key_86cad9&oauth_nonce=225579211881198842005988698334675835446&oauth_signature=26g7wHTtNO6ZWJaLltcueppHYiI%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1199645624&oauth_token=token_411a7f&oauth_version=1.0", uri.query.split("&").sort.join('&')
137-
assert_equal nil, request.body
164+
assert_equal "", request.body
138165
assert_equal nil, request['authorization']
139166
end
140167

@@ -152,6 +179,22 @@ def test_that_using_post_with_uri_and_form_params_works
152179
assert_equal nil, request['authorization']
153180
end
154181

182+
def test_that_using_post_with_uri_and_data_works
183+
request = Net::HTTP::Post.new(@request_uri.path + "?" + request_parameters_to_s)
184+
request.body = "data"
185+
request.content_type = 'text/ascii'
186+
request.oauth!(@http, @consumer, @token, {:scheme => :query_string, :nonce => @nonce, :timestamp => @timestamp})
187+
188+
assert_equal 'POST', request.method
189+
uri = URI.parse(request.path)
190+
assert_equal '/test', uri.path
191+
assert_equal nil, uri.fragment
192+
assert_equal "data", request.body
193+
assert_equal 'text/ascii', request.content_type
194+
assert_equal "key=value&oauth_body_hash=oXyaqmHoChv3HQ2FCvTluqmAC70%3D&oauth_consumer_key=consumer_key_86cad9&oauth_nonce=225579211881198842005988698334675835446&oauth_signature=MHRKU42iVHU4Ke9kBUDa9Zw6IAM%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1199645624&oauth_token=token_411a7f&oauth_version=1.0", uri.query.split("&").sort.join('&')
195+
assert_equal nil, request['authorization']
196+
end
197+
155198

156199
def test_example_from_specs
157200
consumer=OAuth::Consumer.new("dpf43f3p2l4k3l03","kd94hf93k423kf44")
@@ -200,12 +243,12 @@ def test_step_by_step_token_request
200243
assert_equal "oauth_token=requestkey&oauth_token_secret=requestsecret",response.body
201244
end
202245

203-
def test_that_put_bodies_not_signed
246+
def test_that_put_bodies_signed
204247
request = Net::HTTP::Put.new(@request_uri.path)
205248
request.body = "<?xml version=\"1.0\"?><foo><bar>baz</bar></foo>"
206249
request["Content-Type"] = "application/xml"
207250
signature_base_string=request.signature_base_string(@http, @consumer, nil, { :nonce => @nonce, :timestamp => @timestamp })
208-
assert_equal "PUT&http%3A%2F%2Fexample.com%2Ftest&oauth_consumer_key%3Dconsumer_key_86cad9%26oauth_nonce%3D225579211881198842005988698334675835446%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1199645624%26oauth_version%3D1.0", signature_base_string
251+
assert_equal "PUT&http%3A%2F%2Fexample.com%2Ftest&oauth_body_hash%3DDvAa1AWdFoH9K%252B%252F2AHm3f6wH27k%253D%26oauth_consumer_key%3Dconsumer_key_86cad9%26oauth_nonce%3D225579211881198842005988698334675835446%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1199645624%26oauth_version%3D1.0", signature_base_string
209252
end
210253

211254
def test_that_put_bodies_not_signed_even_if_form_urlencoded
@@ -222,12 +265,12 @@ def test_that_post_bodies_signed_if_form_urlencoded
222265
assert_equal "POST&http%3A%2F%2Fexample.com%2Ftest&key2%3Dvalue2%26oauth_consumer_key%3Dconsumer_key_86cad9%26oauth_nonce%3D225579211881198842005988698334675835446%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1199645624%26oauth_version%3D1.0", signature_base_string
223266
end
224267

225-
def test_that_post_bodies_not_signed_if_other_content_type
268+
def test_that_post_bodies_signed_if_other_content_type
226269
request = Net::HTTP::Post.new(@request_uri.path)
227270
request.body = "<?xml version=\"1.0\"?><foo><bar>baz</bar></foo>"
228271
request["Content-Type"] = "application/xml"
229272
signature_base_string=request.signature_base_string(@http, @consumer, nil, { :nonce => @nonce, :timestamp => @timestamp })
230-
assert_equal "POST&http%3A%2F%2Fexample.com%2Ftest&oauth_consumer_key%3Dconsumer_key_86cad9%26oauth_nonce%3D225579211881198842005988698334675835446%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1199645624%26oauth_version%3D1.0", signature_base_string
273+
assert_equal "POST&http%3A%2F%2Fexample.com%2Ftest&oauth_body_hash%3DDvAa1AWdFoH9K%252B%252F2AHm3f6wH27k%253D%26oauth_consumer_key%3Dconsumer_key_86cad9%26oauth_nonce%3D225579211881198842005988698334675835446%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1199645624%26oauth_version%3D1.0", signature_base_string
231274
end
232275

233276
protected

0 commit comments

Comments
 (0)