simple_oauth 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -19,3 +19,4 @@ rdoc
19
19
  pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
@@ -0,0 +1,18 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ simple_oauth (0.1.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ mocha (0.9.8)
10
+ rake
11
+ rake (0.8.7)
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ mocha
18
+ simple_oauth!
@@ -7,7 +7,7 @@ module SimpleOAuth
7
7
  module Version
8
8
  MAJOR = 0
9
9
  MINOR = 1
10
- PATCH = 0
10
+ PATCH = 1
11
11
  STRING = [MAJOR, MINOR, PATCH].join('.')
12
12
  end
13
13
 
@@ -69,7 +69,7 @@ module SimpleOAuth
69
69
 
70
70
  private
71
71
  def normalized_attributes
72
- signed_attributes.sort_by(&:to_s).map{|k,v| %(#{k}="#{self.class.encode(v)}") }.join(', ')
72
+ signed_attributes.sort_by{|k,v| k.to_s }.map{|k,v| %(#{k}="#{self.class.encode(v)}") }.join(', ')
73
73
  end
74
74
 
75
75
  def signed_attributes
@@ -85,25 +85,19 @@ module SimpleOAuth
85
85
  end
86
86
 
87
87
  def hmac_sha1_signature
88
- Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), secret, signature_base)).chomp
89
- end
90
-
91
- def rsa_sha1_signature
92
- Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), options[:private_key].to_s, signature_base)).chomp
88
+ Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, secret, signature_base)).chomp.gsub(/\n/, '')
93
89
  end
94
90
 
95
91
  def secret
96
92
  options.values_at(:consumer_secret, :token_secret).map{|v| self.class.encode(v) }.join('&')
97
93
  end
98
94
 
99
- alias_method :plaintext_signature, :secret
100
-
101
95
  def signature_base
102
96
  [method, url, normalized_params].map{|v| self.class.encode(v) }.join('&')
103
97
  end
104
98
 
105
99
  def normalized_params
106
- signature_params.sort_by(&:to_s).map{|p| p.map{|v| self.class.encode(v) }.join('=') }.join('&')
100
+ signature_params.map{|p| p.map{|v| self.class.encode(v) } }.sort.map{|p| p.join('=') }.join('&')
107
101
  end
108
102
 
109
103
  def signature_params
@@ -113,5 +107,15 @@ module SimpleOAuth
113
107
  def url_params
114
108
  CGI.parse(@uri.query || '').inject([]){|p,(k,vs)| p + vs.map{|v| [k, v] } }
115
109
  end
110
+
111
+ def rsa_sha1_signature
112
+ Base64.encode64(private_key.sign(OpenSSL::Digest::SHA1.new, signature_base)).chomp.gsub(/\n/, '')
113
+ end
114
+
115
+ def private_key
116
+ OpenSSL::PKey::RSA.new(options[:consumer_secret])
117
+ end
118
+
119
+ alias_method :plaintext_signature, :secret
116
120
  end
117
121
  end
@@ -0,0 +1,56 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{simple_oauth}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Steve Richert"]
12
+ s.date = %q{2010-10-13}
13
+ s.description = %q{Simply builds and verifies OAuth headers}
14
+ s.email = %q{steve.richert@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "init.rb",
27
+ "lib/simple_oauth.rb",
28
+ "simple_oauth.gemspec",
29
+ "test/helper.rb",
30
+ "test/rsa_private_key",
31
+ "test/simple_oauth_test.rb"
32
+ ]
33
+ s.homepage = %q{http://github.com/laserlemon/simple_oauth}
34
+ s.rdoc_options = ["--charset=UTF-8"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.3.7}
37
+ s.summary = %q{Simply builds and verifies OAuth headers}
38
+ s.test_files = [
39
+ "test/helper.rb",
40
+ "test/simple_oauth_test.rb"
41
+ ]
42
+
43
+ if s.respond_to? :specification_version then
44
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
45
+ s.specification_version = 3
46
+
47
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
48
+ s.add_development_dependency(%q<mocha>, [">= 0"])
49
+ else
50
+ s.add_dependency(%q<mocha>, [">= 0"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<mocha>, [">= 0"])
54
+ end
55
+ end
56
+
@@ -0,0 +1,16 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V
3
+ A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d
4
+ 7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ
5
+ hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H
6
+ X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm
7
+ uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw
8
+ rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z
9
+ zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn
10
+ qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG
11
+ WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno
12
+ cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+
13
+ 3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8
14
+ AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54
15
+ Lw03eHTNQghS0A==
16
+ -----END PRIVATE KEY-----
@@ -1,21 +1,6 @@
1
1
  require 'helper'
2
2
 
3
3
  class SimpleOAuthTest < Test::Unit::TestCase
4
- def test_initialize
5
- header = SimpleOAuth::Header.new(:get, 'HTTPS://api.TWITTER.com:443/1/statuses/friendships.json#anchor', {})
6
-
7
- # HTTP method should be an uppercase string.
8
- #
9
- # See: http://oauth.net/core/1.0/#rfc.section.9.1.3
10
- assert_equal 'GET', header.method
11
-
12
- # Request URL should downcase the scheme and authority parts as well as
13
- # remove the query and fragment parts.
14
- #
15
- # See: http://oauth.net/core/1.0/#rfc.section.9.1.2
16
- assert_equal 'https://api.twitter.com/1/statuses/friendships.json', header.url
17
- end
18
-
19
4
  def test_default_options
20
5
  # Default header options should change with each call due to generation of
21
6
  # a unique "timestamp" and "nonce" value combination.
@@ -33,24 +18,6 @@ class SimpleOAuthTest < Test::Unit::TestCase
33
18
  assert_equal '1.0', default_options[:version]
34
19
  end
35
20
 
36
- def test_attributes
37
- attribute_options = SimpleOAuth::Header::ATTRIBUTE_KEYS.inject({}){|o,a| o.merge(a => a.to_s.upcase) }
38
- options = attribute_options.merge(:other => 'OTHER')
39
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {}, options)
40
- attributes = header.send(:attributes)
41
-
42
- # OAuth header attributes are all to begin with the "oauth_" prefix.
43
- assert attributes.all?{|k,v| k.to_s =~ /^oauth_/ }
44
-
45
- # Custom options not included in the list of valid attribute keys should
46
- # not be included in the header attributes.
47
- assert !attributes.key?(:oauth_other)
48
-
49
- # Valid attribute option values should be preserved.
50
- assert_equal attribute_options.size, attributes.size
51
- assert attributes.all?{|k,v| k.to_s == "oauth_#{v.downcase}" }
52
- end
53
-
54
21
  def test_encode
55
22
  # Non-word characters should be URL encoded...
56
23
  [' ', '!', '@', '#', '$', '%', '^', '&'].each do |character|
@@ -65,85 +32,122 @@ class SimpleOAuthTest < Test::Unit::TestCase
65
32
  end
66
33
  end
67
34
 
68
- def test_url_params
69
- # A URL with no query parameters should produce empty +url_params+
70
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {})
71
- assert_equal [], header.send(:url_params)
35
+ def test_decode
36
+ # Pending
37
+ end
72
38
 
73
- # A URL with query parameters should return a hash having array values
74
- # containing the given query parameters.
75
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json?test=TEST', {})
76
- url_params = header.send(:url_params)
77
- assert_kind_of Array, url_params
78
- assert_equal [['test', 'TEST']], url_params
39
+ def test_parse
40
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {})
41
+ parsed_options = SimpleOAuth::Header.parse(header)
79
42
 
80
- # If a query parameter is repeated, the values should be sorted.
81
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json?test=1&test=2', {})
82
- assert_equal [['test', '1'], ['test', '2']], header.send(:url_params)
43
+ # Parsed options should be a Hash.
44
+ assert_kind_of Hash, parsed_options
45
+
46
+ # Parsed options should equal the options used to build the header, along
47
+ # with the additional signature.
48
+ assert_equal header.options, parsed_options.reject{|k,v| k == :signature }
83
49
  end
84
50
 
85
- def test_signature_params
86
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {})
87
- header.stubs(:attributes).returns(:attribute => 'ATTRIBUTE')
88
- header.stubs(:params).returns('param' => 'PARAM')
89
- header.stubs(:url_params).returns([['url_param', '1'], ['url_param', '2']])
51
+ def test_initialize
52
+ header = SimpleOAuth::Header.new(:get, 'HTTPS://api.TWITTER.com:443/1/statuses/friendships.json#anchor', {})
90
53
 
91
- # Should combine OAuth header attributes, body parameters and URL
92
- # parameters into an array of key value pairs.
93
- signature_params = header.send(:signature_params)
94
- assert_kind_of Array, signature_params
95
- assert_equal [:attribute, 'param', 'url_param', 'url_param'], signature_params.map(&:first)
96
- assert_equal ['ATTRIBUTE', 'PARAM', '1', '2'], signature_params.map(&:last)
54
+ # HTTP method should be an uppercase string.
55
+ #
56
+ # See: http://oauth.net/core/1.0/#rfc.section.9.1.3
57
+ assert_equal 'GET', header.method
58
+
59
+ # Request URL should downcase the scheme and authority parts as well as
60
+ # remove the query and fragment parts.
61
+ #
62
+ # See: http://oauth.net/core/1.0/#rfc.section.9.1.2
63
+ assert_equal 'https://api.twitter.com/1/statuses/friendships.json', header.url
97
64
  end
98
65
 
99
- def test_normalized_params
100
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {})
101
- header.stubs(:signature_params).returns([['A', '4'], ['B', '3'], ['B', '2'], ['C', '1'], ['D[]', '0 ']])
66
+ def test_url
67
+ # Pending
68
+ end
102
69
 
103
- # The +normalized_params+ string should join key=value pairs with
104
- # ampersands.
105
- signature_params = header.send(:signature_params)
106
- normalized_params = header.send(:normalized_params)
107
- parts = normalized_params.split('&')
108
- pairs = parts.map{|p| p.split('=') }
109
- assert_kind_of String, normalized_params
110
- assert_equal signature_params.size, parts.size
111
- assert pairs.all?{|p| p.size == 2 }
70
+ def test_to_s
71
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {})
72
+ assert_equal "OAuth #{header.send(:normalized_attributes)}", header.to_s
73
+ end
112
74
 
113
- # The signature parameters should be sorted and the keys/values URL encoded
114
- # first.
115
- assert_equal signature_params.sort_by(&:to_s), pairs.map{|k,v| [URI.decode(k), URI.decode(v)] }
75
+ def test_valid?
76
+ # When given consumer and token secrets, those secrets must be passed into
77
+ # the parsed header validation in order for the validity check to pass.
78
+ secrets = {:consumer_secret => 'CONSUMER_SECRET', :token_secret => 'TOKEN_SECRET'}
79
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, secrets)
80
+ parsed_header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, header)
81
+ assert !parsed_header.valid?
82
+ assert parsed_header.valid?(secrets)
83
+
84
+ # Using the RSA-SHA1 signature method, the consumer secret must be a valid
85
+ # RSA private key. When parsing the header on the server side, the same
86
+ # consumer secret must be included in order for the header to validate.
87
+ secrets = {:consumer_secret => File.read('test/rsa_private_key')}
88
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, secrets.merge(:signature_method => 'RSA-SHA1'))
89
+ parsed_header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, header)
90
+ assert_raise(TypeError){ parsed_header.valid? }
91
+ assert parsed_header.valid?(secrets)
92
+
93
+ # Like the default HMAC-RSA1 signature method, the PLAINTEXT method
94
+ # requires use of both a consumer secret and a token secret. A parsed
95
+ # header will not validate without these secret values.
96
+ secrets = {:consumer_secret => 'CONSUMER_SECRET', :token_secret => 'TOKEN_SECRET'}
97
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, secrets.merge(:signature_method => 'PLAINTEXT'))
98
+ parsed_header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, header)
99
+ assert !parsed_header.valid?
100
+ assert parsed_header.valid?(secrets)
116
101
  end
117
102
 
118
- def test_signature_base
119
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {})
120
- header.stubs(:method).returns('METHOD')
121
- header.stubs(:url).returns('URL')
122
- header.stubs(:normalized_params).returns('NORMALIZED_PARAMS')
103
+ def test_normalized_attributes
104
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {})
105
+ header.stubs(:signed_attributes).returns(:d => 1, :c => 2, :b => 3, :a => 4)
123
106
 
124
- # Should combine HTTP method, URL and normalized parameters string using
125
- # ampersands.
126
- assert_equal 'METHOD&URL&NORMALIZED_PARAMS', header.send(:signature_base)
107
+ # Should return the OAuth header attributes, sorted by name, with quoted
108
+ # values and comma-separated.
109
+ assert_equal 'a="4", b="3", c="2", d="1"', header.send(:normalized_attributes)
127
110
 
128
- header.stubs(:method).returns('ME#HOD')
129
- header.stubs(:url).returns('U#L')
130
- header.stubs(:normalized_params).returns('NORMAL#ZED_PARAMS')
111
+ # Values should also be URL encoded.
112
+ header.stubs(:signed_attributes).returns(1 => '!', 2 => '@', 3 => '#', 4 => '$')
113
+ assert_equal '1="%21", 2="%40", 3="%23", 4="%24"', header.send(:normalized_attributes)
114
+ end
131
115
 
132
- # Each of the three combined values should be URL encoded.
133
- assert_equal 'ME%23HOD&U%23L&NORMAL%23ZED_PARAMS', header.send(:signature_base)
116
+ def test_signed_attributes
117
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {})
118
+ assert header.send(:signed_attributes).keys.include?(:oauth_signature)
134
119
  end
135
120
 
136
- def test_secret
137
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {})
138
- header.stubs(:options).returns(:consumer_secret => 'CONSUMER_SECRET', :token_secret => 'TOKEN_SECRET')
121
+ def test_attributes
122
+ attribute_options = SimpleOAuth::Header::ATTRIBUTE_KEYS.inject({}){|o,a| o.merge(a => a.to_s.upcase) }
123
+ options = attribute_options.merge(:other => 'OTHER')
124
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {}, options)
125
+ attributes = header.send(:attributes)
139
126
 
140
- # Should combine the consumer and token secrets with an ampersand.
141
- assert_equal 'CONSUMER_SECRET&TOKEN_SECRET', header.send(:secret)
127
+ # OAuth header attributes are all to begin with the "oauth_" prefix.
128
+ assert attributes.all?{|k,v| k.to_s =~ /^oauth_/ }
142
129
 
143
- header.stubs(:options).returns(:consumer_secret => 'CONSUM#R_SECRET', :token_secret => 'TOKEN_S#CRET')
130
+ # Custom options not included in the list of valid attribute keys should
131
+ # not be included in the header attributes.
132
+ assert !attributes.key?(:oauth_other)
144
133
 
145
- # Should URL encode each secret value before combination.
146
- assert_equal 'CONSUM%23R_SECRET&TOKEN_S%23CRET', header.send(:secret)
134
+ # Valid attribute option values should be preserved.
135
+ assert_equal attribute_options.size, attributes.size
136
+ assert attributes.all?{|k,v| k.to_s == "oauth_#{v.downcase}" }
137
+ end
138
+
139
+ def test_signature
140
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, :signature_method => 'HMAC-SHA1')
141
+ header.expects(:hmac_sha1_signature).once.returns('HMAC_SHA1_SIGNATURE')
142
+ assert_equal 'HMAC_SHA1_SIGNATURE', header.send(:signature)
143
+
144
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, :signature_method => 'RSA-SHA1')
145
+ header.expects(:rsa_sha1_signature).once.returns('RSA_SHA1_SIGNATURE')
146
+ assert_equal 'RSA_SHA1_SIGNATURE', header.send(:signature)
147
+
148
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, :signature_method => 'PLAINTEXT')
149
+ header.expects(:plaintext_signature).once.returns('PLAINTEXT_SIGNATURE')
150
+ assert_equal 'PLAINTEXT_SIGNATURE', header.send(:signature)
147
151
  end
148
152
 
149
153
  def test_hmac_sha1_signature
@@ -172,89 +176,120 @@ class SimpleOAuthTest < Test::Unit::TestCase
172
176
  assert_equal successful, header.to_s
173
177
  end
174
178
 
175
- def test_signature
176
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, :signature_method => 'HMAC-SHA1')
177
- header.expects(:hmac_sha1_signature).once.returns('HMAC_SHA1_SIGNATURE')
178
- assert_equal 'HMAC_SHA1_SIGNATURE', header.send(:signature)
179
+ def test_secret
180
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {})
181
+ header.stubs(:options).returns(:consumer_secret => 'CONSUMER_SECRET', :token_secret => 'TOKEN_SECRET')
179
182
 
180
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, :signature_method => 'RSA-SHA1', :private_key => 'PRIVATE_KEY')
181
- header.expects(:rsa_sha1_signature).once.returns('RSA_SHA1_SIGNATURE')
182
- assert_equal 'RSA_SHA1_SIGNATURE', header.send(:signature)
183
+ # Should combine the consumer and token secrets with an ampersand.
184
+ assert_equal 'CONSUMER_SECRET&TOKEN_SECRET', header.send(:secret)
183
185
 
184
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, :signature_method => 'PLAINTEXT')
185
- header.expects(:plaintext_signature).once.returns('PLAINTEXT_SIGNATURE')
186
- assert_equal 'PLAINTEXT_SIGNATURE', header.send(:signature)
186
+ header.stubs(:options).returns(:consumer_secret => 'CONSUM#R_SECRET', :token_secret => 'TOKEN_S#CRET')
187
+
188
+ # Should URL encode each secret value before combination.
189
+ assert_equal 'CONSUM%23R_SECRET&TOKEN_S%23CRET', header.send(:secret)
187
190
  end
188
191
 
189
- def test_signed_attributes
190
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {})
191
- assert header.send(:signed_attributes).keys.include?(:oauth_signature)
192
+ def test_signature_base
193
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {})
194
+ header.stubs(:method).returns('METHOD')
195
+ header.stubs(:url).returns('URL')
196
+ header.stubs(:normalized_params).returns('NORMALIZED_PARAMS')
197
+
198
+ # Should combine HTTP method, URL and normalized parameters string using
199
+ # ampersands.
200
+ assert_equal 'METHOD&URL&NORMALIZED_PARAMS', header.send(:signature_base)
201
+
202
+ header.stubs(:method).returns('ME#HOD')
203
+ header.stubs(:url).returns('U#L')
204
+ header.stubs(:normalized_params).returns('NORMAL#ZED_PARAMS')
205
+
206
+ # Each of the three combined values should be URL encoded.
207
+ assert_equal 'ME%23HOD&U%23L&NORMAL%23ZED_PARAMS', header.send(:signature_base)
192
208
  end
193
209
 
194
- def test_normalized_attributes
195
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {})
196
- header.stubs(:signed_attributes).returns(:d => 1, :c => 2, :b => 3, :a => 4)
210
+ def test_normalized_params
211
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {})
212
+ header.stubs(:signature_params).returns([['A', '4'], ['B', '3'], ['B', '2'], ['C', '1'], ['D[]', '0 ']])
197
213
 
198
- # Should return the OAuth header attributes, sorted by name, with quoted
199
- # values and comma-separated.
200
- assert_equal 'a="4", b="3", c="2", d="1"', header.send(:normalized_attributes)
214
+ # The +normalized_params+ string should join key=value pairs with
215
+ # ampersands.
216
+ signature_params = header.send(:signature_params)
217
+ normalized_params = header.send(:normalized_params)
218
+ parts = normalized_params.split('&')
219
+ pairs = parts.map{|p| p.split('=') }
220
+ assert_kind_of String, normalized_params
221
+ assert_equal signature_params.size, parts.size
222
+ assert pairs.all?{|p| p.size == 2 }
201
223
 
202
- # Values should also be URL encoded.
203
- header.stubs(:signed_attributes).returns(1 => '!', 2 => '@', 3 => '#', 4 => '$')
204
- assert_equal '1="%21", 2="%40", 3="%23", 4="%24"', header.send(:normalized_attributes)
224
+ # The signature parameters should be sorted and the keys/values URL encoded
225
+ # first.
226
+ assert_equal signature_params.sort_by(&:to_s), pairs.map{|k,v| [URI.decode(k), URI.decode(v)] }
205
227
  end
206
228
 
207
- def test_to_s
208
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {})
209
- assert_equal "OAuth #{header.send(:normalized_attributes)}", header.to_s
229
+ def test_signature_params
230
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {})
231
+ header.stubs(:attributes).returns(:attribute => 'ATTRIBUTE')
232
+ header.stubs(:params).returns('param' => 'PARAM')
233
+ header.stubs(:url_params).returns([['url_param', '1'], ['url_param', '2']])
234
+
235
+ # Should combine OAuth header attributes, body parameters and URL
236
+ # parameters into an array of key value pairs.
237
+ signature_params = header.send(:signature_params)
238
+ assert_kind_of Array, signature_params
239
+ assert_equal [:attribute, 'param', 'url_param', 'url_param'], signature_params.map(&:first)
240
+ assert_equal ['ATTRIBUTE', 'PARAM', '1', '2'], signature_params.map(&:last)
210
241
  end
211
242
 
212
- def test_parse
213
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {})
214
- parsed_options = SimpleOAuth::Header.parse(header)
243
+ def test_url_params
244
+ # A URL with no query parameters should produce empty +url_params+
245
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {})
246
+ assert_equal [], header.send(:url_params)
215
247
 
216
- # Parsed options should be a Hash.
217
- assert_kind_of Hash, parsed_options
248
+ # A URL with query parameters should return a hash having array values
249
+ # containing the given query parameters.
250
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json?test=TEST', {})
251
+ url_params = header.send(:url_params)
252
+ assert_kind_of Array, url_params
253
+ assert_equal [['test', 'TEST']], url_params
218
254
 
219
- # Parsed options should equal the options used to build the header, along
220
- # with the additional signature.
221
- assert_equal header.options, parsed_options.reject{|k,v| k == :signature }
255
+ # If a query parameter is repeated, the values should be sorted.
256
+ header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json?test=1&test=2', {})
257
+ assert_equal [['test', '1'], ['test', '2']], header.send(:url_params)
222
258
  end
223
259
 
224
- def test_valid
225
- # With no consumer or token secrets, built headers should be valid when
226
- # parsed without secrets, regardless of signature method.
227
- ['HMAC-SHA1', 'RSA-SHA1', 'PLAINTEXT'].each do |signature_method|
228
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, :signature_method => signature_method)
229
- parsed_header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, header)
230
- assert_equal signature_method, parsed_header.options[:signature_method]
231
- assert parsed_header.valid?
232
- end
233
-
234
- # When given consumer and token secrets, those secrets must be passed into
235
- # the parsed header validation in order for the validity check to pass.
236
- secrets = {:consumer_secret => 'CONSUMER_SECRET', :token_secret => 'TOKEN_SECRET'}
237
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, secrets)
238
- parsed_header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, header)
239
- assert !parsed_header.valid?
240
- assert parsed_header.valid?(secrets)
260
+ def test_rsa_sha1_signature
261
+ # Sample request taken from:
262
+ # http://wiki.oauth.net/TestCases
263
+ options = {
264
+ :consumer_key => 'dpf43f3p2l4k3l03',
265
+ :consumer_secret => File.read('test/rsa_private_key'),
266
+ :nonce => '13917289812797014437',
267
+ :signature_method => 'RSA-SHA1',
268
+ :timestamp => '1196666512'
269
+ }
270
+ successful = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03", oauth_nonce="13917289812797014437", oauth_signature="jvTp%2FwX1TYtByB1m%2BPbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2%2F9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW%2F%2Fe%2BRinhejgCuzoH26dyF8iY2ZZ%2F5D1ilgeijhV%2FvBka5twt399mXwaYdCwFYE%3D", oauth_signature_method="RSA-SHA1", oauth_timestamp="1196666512", oauth_version="1.0"'
271
+ header = SimpleOAuth::Header.new(:get, 'http://photos.example.net/photos', {:file => 'vacaction.jpg', :size => 'original'}, options)
272
+ assert_equal successful, header.to_s
273
+ end
241
274
 
242
- # Using the RSA-SHA1 signature method, a private key should be included
243
- # with the options. When parsing the header on the server side, the
244
- # the private key must be included in order for the header to validate.
245
- secrets = {:private_key => 'PRIVATE_KEY'}
246
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, secrets.merge(:signature_method => 'RSA-SHA1'))
247
- parsed_header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, header)
248
- assert !parsed_header.valid?
249
- assert parsed_header.valid?(secrets)
275
+ def test_private_key
276
+ # Pending
277
+ end
250
278
 
251
- # Like the default HMAC-RSA1 signature method, the PLAINTEXT method
252
- # requires use of both a consumer secret and a token secret. A parsed
253
- # header will not validate without these secret values.
254
- secrets = {:consumer_secret => 'CONSUMER_SECRET', :token_secret => 'TOKEN_SECRET'}
255
- header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, secrets.merge(:signature_method => 'PLAINTEXT'))
256
- parsed_header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, header)
257
- assert !parsed_header.valid?
258
- assert parsed_header.valid?(secrets)
279
+ def plaintext_signature
280
+ # Sample request taken from:
281
+ # http://oauth.googlecode.com/svn/code/javascript/example/signature.html
282
+ options = {
283
+ :consumer_key => 'abcd',
284
+ :consumer_secret => 'efgh',
285
+ :nonce => 'oLKtec51GQy',
286
+ :signature_method => 'PLAINTEXT',
287
+ :timestamp => '1286977095',
288
+ :token => 'ijkl',
289
+ :token_secret => 'mnop'
290
+ }
291
+ successful = 'OAuth oauth_consumer_key="abcd", oauth_nonce="oLKtec51GQy", oauth_signature="efgh%26mnop", oauth_signature_method="PLAINTEXT", oauth_timestamp="1286977095", oauth_token="ijkl", oauth_version="1.0"'
292
+ header = SimpleOAuth::Header.new(:get, 'http://host.net/resource?name=value', {:name => 'value'}, options)
293
+ assert_equal successful, header.to_s
259
294
  end
260
295
  end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_oauth
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 25
4
5
  prerelease: false
5
6
  segments:
6
7
  - 0
7
8
  - 1
8
- - 0
9
- version: 0.1.0
9
+ - 1
10
+ version: 0.1.1
10
11
  platform: ruby
11
12
  authors:
12
13
  - Steve Richert
@@ -14,7 +15,7 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-10-11 00:00:00 -04:00
18
+ date: 2010-10-13 00:00:00 -04:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
@@ -25,6 +26,7 @@ dependencies:
25
26
  requirements:
26
27
  - - ">="
27
28
  - !ruby/object:Gem::Version
29
+ hash: 3
28
30
  segments:
29
31
  - 0
30
32
  version: "0"
@@ -41,12 +43,16 @@ extra_rdoc_files:
41
43
  - README.rdoc
42
44
  files:
43
45
  - .gitignore
46
+ - Gemfile
47
+ - Gemfile.lock
44
48
  - LICENSE
45
49
  - README.rdoc
46
50
  - Rakefile
47
51
  - init.rb
48
52
  - lib/simple_oauth.rb
53
+ - simple_oauth.gemspec
49
54
  - test/helper.rb
55
+ - test/rsa_private_key
50
56
  - test/simple_oauth_test.rb
51
57
  has_rdoc: true
52
58
  homepage: http://github.com/laserlemon/simple_oauth
@@ -62,6 +68,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
62
68
  requirements:
63
69
  - - ">="
64
70
  - !ruby/object:Gem::Version
71
+ hash: 3
65
72
  segments:
66
73
  - 0
67
74
  version: "0"
@@ -70,6 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
77
  requirements:
71
78
  - - ">="
72
79
  - !ruby/object:Gem::Version
80
+ hash: 3
73
81
  segments:
74
82
  - 0
75
83
  version: "0"