simple_oauth 0.1.0
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 +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +55 -0
- data/init.rb +1 -0
- data/lib/simple_oauth.rb +117 -0
- data/test/helper.rb +4 -0
- data/test/simple_oauth_test.rb +260 -0
- metadata +85 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Steve Richert
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= simple_oauth
|
2
|
+
|
3
|
+
Simply builds and verifies OAuth headers
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2010 Steve Richert. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require './lib/simple_oauth'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'jeweler'
|
9
|
+
Jeweler::Tasks.new do |gem|
|
10
|
+
gem.name = 'simple_oauth'
|
11
|
+
gem.version = SimpleOAuth::Version::STRING
|
12
|
+
gem.summary = 'Simply builds and verifies OAuth headers'
|
13
|
+
gem.description = 'Simply builds and verifies OAuth headers'
|
14
|
+
gem.email = 'steve.richert@gmail.com'
|
15
|
+
gem.homepage = 'http://github.com/laserlemon/simple_oauth'
|
16
|
+
gem.authors = ['Steve Richert']
|
17
|
+
gem.add_development_dependency 'mocha'
|
18
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
19
|
+
end
|
20
|
+
Jeweler::GemcutterTasks.new
|
21
|
+
rescue LoadError
|
22
|
+
puts 'Jeweler is not available. Install it with: gem install jeweler'
|
23
|
+
end
|
24
|
+
|
25
|
+
Rake::TestTask.new do |test|
|
26
|
+
test.libs << 'lib' << 'test'
|
27
|
+
test.pattern = 'test/**/*_test.rb'
|
28
|
+
test.verbose = true
|
29
|
+
end
|
30
|
+
|
31
|
+
task :test => :check_dependencies
|
32
|
+
|
33
|
+
task :default => :test
|
34
|
+
|
35
|
+
begin
|
36
|
+
require 'rcov/rcovtask'
|
37
|
+
Rcov::RcovTask.new do |rcov|
|
38
|
+
rcov.libs << 'lib' << 'test'
|
39
|
+
rcov.pattern = 'test/**/*_test.rb'
|
40
|
+
rcov.verbose = true
|
41
|
+
rcov.rcov_opts << '--exclude "gems/*"'
|
42
|
+
end
|
43
|
+
rescue LoadError
|
44
|
+
task :rcov do
|
45
|
+
abort 'RCov is not available. Install it with: gem install rcov'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Rake::RDocTask.new do |rdoc|
|
50
|
+
version = SimpleOAuth::Version::STRING
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "simple_oauth #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path('../lib/simple_oauth', __FILE__)
|
data/lib/simple_oauth.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'cgi'
|
3
|
+
require 'openssl'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module SimpleOAuth
|
7
|
+
module Version
|
8
|
+
MAJOR = 0
|
9
|
+
MINOR = 1
|
10
|
+
PATCH = 0
|
11
|
+
STRING = [MAJOR, MINOR, PATCH].join('.')
|
12
|
+
end
|
13
|
+
|
14
|
+
class Header
|
15
|
+
ATTRIBUTE_KEYS = [:consumer_key, :nonce, :signature_method, :timestamp, :token, :version]
|
16
|
+
|
17
|
+
def self.default_options
|
18
|
+
{
|
19
|
+
:nonce => OpenSSL::Random.random_bytes(16).unpack('H*')[0],
|
20
|
+
:signature_method => 'HMAC-SHA1',
|
21
|
+
:timestamp => Time.now.to_i.to_s,
|
22
|
+
:version => '1.0'
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.encode(value)
|
27
|
+
URI.encode(value.to_s, /[^\w\-\.\~]/)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.decode(value)
|
31
|
+
URI.decode(value.to_s)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.parse(header)
|
35
|
+
header.to_s.sub(/^OAuth\s/, '').split(', ').inject({}) do |attributes, pair|
|
36
|
+
match = pair.match(/^(\w+)\=\"([^\"]*)\"$/)
|
37
|
+
attributes.merge(match[1].sub(/^oauth_/, '').to_sym => decode(match[2]))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :method, :params, :options
|
42
|
+
|
43
|
+
def initialize(method, url, params, oauth = {})
|
44
|
+
@method = method.to_s.upcase
|
45
|
+
@uri = URI.parse(url).tap do |uri|
|
46
|
+
uri.scheme = uri.scheme.downcase
|
47
|
+
uri.normalize!
|
48
|
+
uri.fragment = nil
|
49
|
+
end
|
50
|
+
@params = params
|
51
|
+
@options = oauth.is_a?(Hash) ? self.class.default_options.merge(oauth) : self.class.parse(oauth)
|
52
|
+
end
|
53
|
+
|
54
|
+
def url
|
55
|
+
@uri.dup.tap{|u| u.query = nil }.to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
"OAuth #{normalized_attributes}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def valid?(secrets = {})
|
63
|
+
original_options = options.dup
|
64
|
+
options.merge!(secrets)
|
65
|
+
valid = options[:signature] == signature
|
66
|
+
options.replace(original_options)
|
67
|
+
valid
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def normalized_attributes
|
72
|
+
signed_attributes.sort_by(&:to_s).map{|k,v| %(#{k}="#{self.class.encode(v)}") }.join(', ')
|
73
|
+
end
|
74
|
+
|
75
|
+
def signed_attributes
|
76
|
+
attributes.merge(:oauth_signature => signature)
|
77
|
+
end
|
78
|
+
|
79
|
+
def attributes
|
80
|
+
ATTRIBUTE_KEYS.inject({}){|a,k| options.key?(k) ? a.merge(:"oauth_#{k}" => options[k]) : a }
|
81
|
+
end
|
82
|
+
|
83
|
+
def signature
|
84
|
+
send(options[:signature_method].downcase.tr('-', '_') + '_signature')
|
85
|
+
end
|
86
|
+
|
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
|
93
|
+
end
|
94
|
+
|
95
|
+
def secret
|
96
|
+
options.values_at(:consumer_secret, :token_secret).map{|v| self.class.encode(v) }.join('&')
|
97
|
+
end
|
98
|
+
|
99
|
+
alias_method :plaintext_signature, :secret
|
100
|
+
|
101
|
+
def signature_base
|
102
|
+
[method, url, normalized_params].map{|v| self.class.encode(v) }.join('&')
|
103
|
+
end
|
104
|
+
|
105
|
+
def normalized_params
|
106
|
+
signature_params.sort_by(&:to_s).map{|p| p.map{|v| self.class.encode(v) }.join('=') }.join('&')
|
107
|
+
end
|
108
|
+
|
109
|
+
def signature_params
|
110
|
+
attributes.to_a + params.to_a + url_params
|
111
|
+
end
|
112
|
+
|
113
|
+
def url_params
|
114
|
+
CGI.parse(@uri.query || '').inject([]){|p,(k,vs)| p + vs.map{|v| [k, v] } }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
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
|
+
def test_default_options
|
20
|
+
# Default header options should change with each call due to generation of
|
21
|
+
# a unique "timestamp" and "nonce" value combination.
|
22
|
+
default_options = SimpleOAuth::Header.default_options
|
23
|
+
assert_not_equal default_options, SimpleOAuth::Header.default_options
|
24
|
+
|
25
|
+
SimpleOAuth::Header.stubs(:default_options).returns(default_options)
|
26
|
+
header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friendships.json', {})
|
27
|
+
|
28
|
+
# Given no options argument, header options defer to the default options.
|
29
|
+
assert_equal default_options, header.options
|
30
|
+
|
31
|
+
# Default options should include a signature method and the OAuth version.
|
32
|
+
assert_equal 'HMAC-SHA1', default_options[:signature_method]
|
33
|
+
assert_equal '1.0', default_options[:version]
|
34
|
+
end
|
35
|
+
|
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
|
+
def test_encode
|
55
|
+
# Non-word characters should be URL encoded...
|
56
|
+
[' ', '!', '@', '#', '$', '%', '^', '&'].each do |character|
|
57
|
+
encoded = SimpleOAuth::Header.encode(character)
|
58
|
+
assert_not_equal character, encoded
|
59
|
+
assert_equal URI.encode(character, /.*/), encoded
|
60
|
+
end
|
61
|
+
|
62
|
+
# ...except for the "-", "." and "~" characters.
|
63
|
+
['-', '.', '~'].each do |character|
|
64
|
+
assert_equal character, SimpleOAuth::Header.encode(character)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
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)
|
72
|
+
|
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
|
79
|
+
|
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)
|
83
|
+
end
|
84
|
+
|
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']])
|
90
|
+
|
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)
|
97
|
+
end
|
98
|
+
|
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 ']])
|
102
|
+
|
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 }
|
112
|
+
|
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)] }
|
116
|
+
end
|
117
|
+
|
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')
|
123
|
+
|
124
|
+
# Should combine HTTP method, URL and normalized parameters string using
|
125
|
+
# ampersands.
|
126
|
+
assert_equal 'METHOD&URL&NORMALIZED_PARAMS', header.send(:signature_base)
|
127
|
+
|
128
|
+
header.stubs(:method).returns('ME#HOD')
|
129
|
+
header.stubs(:url).returns('U#L')
|
130
|
+
header.stubs(:normalized_params).returns('NORMAL#ZED_PARAMS')
|
131
|
+
|
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)
|
134
|
+
end
|
135
|
+
|
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')
|
139
|
+
|
140
|
+
# Should combine the consumer and token secrets with an ampersand.
|
141
|
+
assert_equal 'CONSUMER_SECRET&TOKEN_SECRET', header.send(:secret)
|
142
|
+
|
143
|
+
header.stubs(:options).returns(:consumer_secret => 'CONSUM#R_SECRET', :token_secret => 'TOKEN_S#CRET')
|
144
|
+
|
145
|
+
# Should URL encode each secret value before combination.
|
146
|
+
assert_equal 'CONSUM%23R_SECRET&TOKEN_S%23CRET', header.send(:secret)
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_hmac_sha1_signature
|
150
|
+
# Reproduce an actual successful call to the Twitter API using the
|
151
|
+
# HMAC-SHA1 signature method, GETting a list of friends.
|
152
|
+
options = {
|
153
|
+
:consumer_key => '8karQBlMg6gFOwcf8kcoYw',
|
154
|
+
:consumer_secret => '3d0vcHyUiiqADpWxolW8nlDIpSWMlyK7YNgc5Qna2M',
|
155
|
+
:nonce => '547fed103e122eecf84c080843eedfe6',
|
156
|
+
#:signature_method => 'HMAC-SHA1',
|
157
|
+
:timestamp => '1286830180',
|
158
|
+
:token => '201425800-Sv4sTcgoffmHGkTCue0JnURT8vrm4DiFAkeFNDkh',
|
159
|
+
:token_secret => 'T5qa1tF57tfDzKmpM89DHsNuhgOY4NT6DlNLsTFcuQ'
|
160
|
+
}
|
161
|
+
successful = 'OAuth oauth_consumer_key="8karQBlMg6gFOwcf8kcoYw", oauth_nonce="547fed103e122eecf84c080843eedfe6", oauth_signature="i9CT6ahDRAlfGX3hKYf78QzXsaw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1286830180", oauth_token="201425800-Sv4sTcgoffmHGkTCue0JnURT8vrm4DiFAkeFNDkh", oauth_version="1.0"'
|
162
|
+
header = SimpleOAuth::Header.new(:get, 'https://api.twitter.com/1/statuses/friends.json', {}, options)
|
163
|
+
assert_equal successful, header.to_s
|
164
|
+
|
165
|
+
# Reproduce a successful Twitter call, POSTing a new status.
|
166
|
+
options.merge!(
|
167
|
+
:nonce => 'b40a3e0f18590ecdcc0e273f7d7c82f8',
|
168
|
+
:timestamp => '1286830181'
|
169
|
+
)
|
170
|
+
successful = 'OAuth oauth_consumer_key="8karQBlMg6gFOwcf8kcoYw", oauth_nonce="b40a3e0f18590ecdcc0e273f7d7c82f8", oauth_signature="mPqSFKejrWWk3ZT9bTQjhO5b2xI%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1286830181", oauth_token="201425800-Sv4sTcgoffmHGkTCue0JnURT8vrm4DiFAkeFNDkh", oauth_version="1.0"'
|
171
|
+
header = SimpleOAuth::Header.new(:post, 'https://api.twitter.com/1/statuses/update.json', {:status => 'hi, again'}, options)
|
172
|
+
assert_equal successful, header.to_s
|
173
|
+
end
|
174
|
+
|
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
|
+
|
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
|
+
|
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)
|
187
|
+
end
|
188
|
+
|
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
|
+
end
|
193
|
+
|
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)
|
197
|
+
|
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)
|
201
|
+
|
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)
|
205
|
+
end
|
206
|
+
|
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
|
210
|
+
end
|
211
|
+
|
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)
|
215
|
+
|
216
|
+
# Parsed options should be a Hash.
|
217
|
+
assert_kind_of Hash, parsed_options
|
218
|
+
|
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 }
|
222
|
+
end
|
223
|
+
|
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)
|
241
|
+
|
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)
|
250
|
+
|
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)
|
259
|
+
end
|
260
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_oauth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Steve Richert
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-11 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: mocha
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :development
|
32
|
+
version_requirements: *id001
|
33
|
+
description: Simply builds and verifies OAuth headers
|
34
|
+
email: steve.richert@gmail.com
|
35
|
+
executables: []
|
36
|
+
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files:
|
40
|
+
- LICENSE
|
41
|
+
- README.rdoc
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- LICENSE
|
45
|
+
- README.rdoc
|
46
|
+
- Rakefile
|
47
|
+
- init.rb
|
48
|
+
- lib/simple_oauth.rb
|
49
|
+
- test/helper.rb
|
50
|
+
- test/simple_oauth_test.rb
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://github.com/laserlemon/simple_oauth
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --charset=UTF-8
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.3.7
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: Simply builds and verifies OAuth headers
|
83
|
+
test_files:
|
84
|
+
- test/helper.rb
|
85
|
+
- test/simple_oauth_test.rb
|