simple_oauth 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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"