soauth 0.1 → 0.2
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/README.markdown +53 -1
- data/Rakefile +7 -8
- data/lib/soauth.rb +60 -61
- data/soauth.gemspec +6 -6
- data/test/soauth_test.rb +47 -47
- metadata +4 -4
data/README.markdown
CHANGED
|
@@ -1 +1,53 @@
|
|
|
1
|
-
|
|
1
|
+
# SOAuth #
|
|
2
|
+
## The "S" is for "Signs" ##
|
|
3
|
+
|
|
4
|
+
*SOAuth* is a Ruby library that **creates HTTP headers for OAuth Authorization** using previously-obtained OAuth keys/secrets. Useful if you want to make your own HTTP request objects instead of using the ones created for you using the [commonly-used OAuth gem](http://github.com/mojodna/oauth).
|
|
5
|
+
|
|
6
|
+
It should be noted that this was developed without edge cases in mind -- it was pretty much abstracted from my "by-hand" signing of OAuth requests in [Prey Fetcher](http://preyfetcher.com), so don't consider it production-quality code (though it [is running in production](http://preyfetcher.com)).
|
|
7
|
+
|
|
8
|
+
Please fork away and send me a pull request if you think you can make it better or handle more use cases.
|
|
9
|
+
|
|
10
|
+
## Installation ##
|
|
11
|
+
|
|
12
|
+
Assuming you have [Gemcutter](http://gemcutter.org/) setup as a gem source, install like any other Ruby gem:
|
|
13
|
+
|
|
14
|
+
gem install soauth
|
|
15
|
+
|
|
16
|
+
If you don't already have [Gemcutter](http://gemcutter.org/) setup as one of your gem sources, install SOAuth with the following command:
|
|
17
|
+
|
|
18
|
+
gem install soauth --source http://gemcutter.org/
|
|
19
|
+
|
|
20
|
+
## Usage ##
|
|
21
|
+
|
|
22
|
+
Create an OAuth header by specifying the URI of the resource you're requesting, your consumer key/secret + access key/secret in a hash, and -- optionally -- any GET params in another hash. Check it out:
|
|
23
|
+
|
|
24
|
+
uri = 'https://twitter.com/direct_messages.json'
|
|
25
|
+
oauth = {
|
|
26
|
+
:consumer_key => "consumer_key",
|
|
27
|
+
:consumer_secret => "consumer_secret",
|
|
28
|
+
:token => "access_key",
|
|
29
|
+
:token_secret => "access_secret"
|
|
30
|
+
}
|
|
31
|
+
params = {
|
|
32
|
+
'count' => "11",
|
|
33
|
+
'since_id' => "5000"
|
|
34
|
+
}
|
|
35
|
+
oauth_header = SOAuth.header(uri, oauth, params)
|
|
36
|
+
|
|
37
|
+
Pretty straightforward. You can use whatever HTTP library you like, just use `oauth_header` as the "Authorization" HTTP header to your request (making sure the request info is the same info you passed to SOAuth). Say you were using **NET::HTTP**:
|
|
38
|
+
|
|
39
|
+
http_uri = URI.parse(uri)
|
|
40
|
+
request = Net::HTTP.new(http_uri.host, http_uri.port)
|
|
41
|
+
request.get(uri.request_uri, {'Authorization', oauth_header})
|
|
42
|
+
|
|
43
|
+
## Why Would I Want This? ##
|
|
44
|
+
|
|
45
|
+
There's already a pretty nice [OAuth library for Ruby out there](http://github.com/mojodna/oauth). But I didn't want to have to use the OAuth library just to make my Authorization headers, and I wanted to be able to plug those headers into whatever HTTP library I wanted (in my case, [Typhoeus](http://github.com/pauldix/typhoeus)). I found using the [OAuth gem](http://github.com/mojodna/oauth) incredibly clunky/overkill for signing requests by hand, so I made SOAuth.
|
|
46
|
+
|
|
47
|
+
## License ##
|
|
48
|
+
|
|
49
|
+
This program is free software; it is distributed under an [MIT-style License](http://fosspass.org/license/mit?author=Matthew+Riley+MacPherson&year=2010).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
Copyright (c) 2010 [Matthew Riley MacPherson](http://lonelyvegan.com).
|
data/Rakefile
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
require 'rake/rdoctask'
|
|
2
2
|
|
|
3
|
-
# Make me some RDoc
|
|
3
|
+
# Make me some RDoc
|
|
4
4
|
Rake::RDocTask.new do |rdoc|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
rdoc.options << '--line-numbers' << '--inline-source'
|
|
5
|
+
files = ['README.markdown', 'LICENSE', 'lib/*.rb', 'test/*.rb']
|
|
6
|
+
rdoc.rdoc_files.add(files)
|
|
7
|
+
rdoc.main = 'README.markdown'
|
|
8
|
+
rdoc.title = 'SOAuth'
|
|
9
|
+
rdoc.rdoc_dir = './doc'
|
|
10
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
|
12
11
|
end
|
data/lib/soauth.rb
CHANGED
|
@@ -3,69 +3,68 @@ require 'openssl'
|
|
|
3
3
|
require 'uri'
|
|
4
4
|
|
|
5
5
|
class SOAuth
|
|
6
|
-
|
|
7
|
-
# Exception raised if signature signing method isn't supported
|
|
8
|
-
# by SOAuth
|
|
9
|
-
class UnsupportedSignatureMethod < Exception; end
|
|
10
|
-
# Exception raised when attempting to create a signature without
|
|
11
|
-
# required OAuth params
|
|
12
|
-
class MissingOAuthParams < Exception; end
|
|
13
|
-
|
|
14
|
-
# Digest key for HMAC-SHA1 signing
|
|
15
|
-
DIGEST = OpenSSL::Digest::Digest.new('sha1')
|
|
16
|
-
# Supported {signature methods}[http://oauth.net/core/1.0/#signing_process];
|
|
17
|
-
# currently, only HMAC-SHA1 is supported
|
|
18
|
-
SUPPORTED_SIGNATURE_METHODS = ["HMAC-SHA1"]
|
|
19
6
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
7
|
+
# Exception raised if signature signing method isn't supported
|
|
8
|
+
# by SOAuth
|
|
9
|
+
class UnsupportedSignatureMethod < Exception; end
|
|
10
|
+
# Exception raised when attempting to create a signature without
|
|
11
|
+
# required OAuth params
|
|
12
|
+
class MissingOAuthParams < Exception; end
|
|
13
|
+
|
|
14
|
+
# Digest key for HMAC-SHA1 signing
|
|
15
|
+
DIGEST = OpenSSL::Digest::Digest.new('sha1')
|
|
16
|
+
# Supported {signature methods}[http://oauth.net/core/1.0/#signing_process];
|
|
17
|
+
# currently, only HMAC-SHA1 is supported
|
|
18
|
+
SUPPORTED_SIGNATURE_METHODS = ["HMAC-SHA1"]
|
|
19
|
+
|
|
20
|
+
# Return an {OAuth "Authorization" HTTP header}[http://oauth.net/core/1.0/#auth_header] from request data
|
|
21
|
+
def header(uri, oauth, params = {}, http_method = :get)
|
|
22
|
+
# Raise an exception if we're missing required OAuth params
|
|
23
|
+
raise MissingOAuthParams if !oauth.is_a?(Hash) || !oauth.has_key?(:consumer_key) || !oauth.has_key?(:consumer_secret) || !oauth.has_key?(:token) || !oauth.has_key?(:token_secret)
|
|
24
|
+
# Make sure we support the signature signing method specified
|
|
25
|
+
raise UnsupportedSignatureMethod unless !oauth[:signature_method] || SUPPORTED_SIGNATURE_METHODS.include?(oauth[:signature_method].to_s)
|
|
26
|
+
|
|
27
|
+
oauth[:signature_method] ||= "HMAC-SHA1" # HMAC-SHA1 seems popular, so it's the default
|
|
28
|
+
oauth[:version] ||= "1.0" # Assumed version, according to the spec
|
|
29
|
+
oauth[:nonce] ||= Base64.encode64(OpenSSL::Random.random_bytes(32)).gsub(/\W/, '')
|
|
30
|
+
oauth[:timestamp] ||= Time.now.to_i
|
|
31
|
+
|
|
32
|
+
# Make a copy of the params hash so we don't add in OAuth stuff
|
|
33
|
+
sig_params = params.dup
|
|
34
|
+
|
|
35
|
+
oauth.each { |k, v|
|
|
36
|
+
# Only use certain OAuth values for the base string
|
|
37
|
+
if [:consumer_key, :token, :signature_method, :version, :nonce, :timestamp].include?(k)
|
|
38
|
+
sig_params['oauth_' + k.to_s] = v
|
|
39
|
+
end
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
secret = "#{escape(oauth[:consumer_secret])}&#{escape(oauth[:token_secret])}"
|
|
43
|
+
sig_base = (http_method||'get').to_s.upcase + '&' + escape(uri) + '&' + normalize(sig_params)
|
|
44
|
+
oauth_signature = Base64.encode64(OpenSSL::HMAC.digest(DIGEST, secret, sig_base)).chomp.gsub(/\n/,'')
|
|
45
|
+
|
|
46
|
+
oauth.merge!(:signature => oauth_signature)
|
|
47
|
+
|
|
48
|
+
%{OAuth } + (oauth.map { |k, v| %{oauth_#{k}="#{escape(v)}", } unless [:consumer_secret, :token_secret].include?(k) }).to_s.chomp(", ")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Utility class used to sign a request and return an
|
|
52
|
+
# {OAuth "Authorization" HTTP header}[http://oauth.net/core/1.0/#auth_header]
|
|
53
|
+
def self.header(uri, oauth, params = {}, http_method = :get)
|
|
54
|
+
new.header(uri, oauth, params, http_method)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
protected
|
|
58
|
+
|
|
59
|
+
# Escape characters in a string according to the {OAuth spec}[http://oauth.net/core/1.0/]
|
|
60
|
+
def escape(value)
|
|
61
|
+
URI::escape(value.to_s, /[^a-zA-Z0-9\-\.\_\~]/) # Unreserved characters -- must not be encoded
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Normalize a string of parameters based on the {OAuth spec}[http://oauth.net/core/1.0/#rfc.section.9.1.1]
|
|
66
65
|
def normalize(params)
|
|
67
66
|
params.sort.map do |k, values|
|
|
68
|
-
|
|
67
|
+
|
|
69
68
|
if values.is_a?(Array)
|
|
70
69
|
# Multiple values were provided for a single key
|
|
71
70
|
# in a hash
|
|
@@ -77,5 +76,5 @@ class SOAuth
|
|
|
77
76
|
end
|
|
78
77
|
end * "%26"
|
|
79
78
|
end
|
|
80
|
-
|
|
79
|
+
|
|
81
80
|
end
|
data/soauth.gemspec
CHANGED
|
@@ -2,15 +2,15 @@ require 'rake'
|
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |s|
|
|
4
4
|
s.name = "soauth"
|
|
5
|
-
s.version = "0.
|
|
6
|
-
|
|
5
|
+
s.version = "0.2"
|
|
6
|
+
s.date = Time.now
|
|
7
7
|
s.authors = ["Matthew Riley MacPherson"]
|
|
8
8
|
s.email = "matt@lonelyvegan.com"
|
|
9
9
|
s.has_rdoc = true
|
|
10
|
-
s.rdoc_options << '--title' << "SOAuth -- Ruby
|
|
11
|
-
s.summary = "
|
|
10
|
+
s.rdoc_options << '--title' << "SOAuth -- Ruby library that creates HTTP headers for OAuth Authorization" << '--main' << 'README.markdown' << '--line-numbers'
|
|
11
|
+
s.summary = "Ruby library that creates HTTP headers for OAuth Authorization using previously-obtained OAuth keys/secrets"
|
|
12
12
|
s.homepage = "http://github.com/tofumatt/soauth"
|
|
13
13
|
s.files = FileList['lib/*.rb', '[A-Z]*', 'soauth.gemspec', 'test/*.rb'].to_a
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
s.test_file = 'test/soauth_test.rb'
|
|
15
|
+
s.add_development_dependency('mocha') # Used to run the tests, that's all...
|
|
16
16
|
end
|
data/test/soauth_test.rb
CHANGED
|
@@ -6,51 +6,51 @@ require 'test/unit'
|
|
|
6
6
|
require 'mocha'
|
|
7
7
|
|
|
8
8
|
class SOAuthTest < Test::Unit::TestCase
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
9
|
+
|
|
10
|
+
# Required OAuth parameters with dummy values -- these
|
|
11
|
+
# keys are the minimum number of keys required to generate
|
|
12
|
+
# an {OAuth "Authorization" HTTP header}[http://oauth.net/core/1.0/#auth_header]
|
|
13
|
+
OAUTH_REQ_PARAMS = {
|
|
14
|
+
:consumer_key => "consumer_key",
|
|
15
|
+
:consumer_secret => "consumer_secret",
|
|
16
|
+
:token => "access_key",
|
|
17
|
+
:token_secret => "access_secret"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# Make sure the custom nonce is used
|
|
21
|
+
def test_custom_nonce
|
|
22
|
+
nonce = Base64.encode64(OpenSSL::Random.random_bytes(32)).gsub(/\W/, '')
|
|
23
|
+
|
|
24
|
+
oauth_params = OAUTH_REQ_PARAMS.merge(:nonce => nonce)
|
|
25
|
+
assert_equal %{oauth_nonce="#{nonce}"}, %{oauth_nonce="#{SOAuth.header('http://lonelyvegan.com/', oauth_params).match(nonce)[0]}"}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Make sure the custom timestamp is used
|
|
29
|
+
def test_custom_nonce
|
|
30
|
+
now = Time.now.to_i
|
|
31
|
+
|
|
32
|
+
oauth_params = OAUTH_REQ_PARAMS.merge(:timestamp => now)
|
|
33
|
+
assert_equal %{oauth_timestamp="#{now}"}, %{oauth_timestamp="#{SOAuth.header('http://lonelyvegan.com/', oauth_params).match(now.to_s)[0]}"}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Generate a header without an explicit OAuth version (assumes {version 1.0}[http://oauth.net/core/1.0/])
|
|
37
|
+
def test_no_version_specified
|
|
38
|
+
# No OAuth version specified
|
|
39
|
+
oauth_params = OAUTH_REQ_PARAMS
|
|
40
|
+
assert SOAuth.header('http://lonelyvegan.com/', OAUTH_REQ_PARAMS)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Generate a header without a {signature method}[http://oauth.net/core/1.0/#signing_process] (assumes "HMAC-SHA1")
|
|
44
|
+
def test_no_signature_method_specified
|
|
45
|
+
# No signature method specified
|
|
46
|
+
oauth_params = OAUTH_REQ_PARAMS
|
|
47
|
+
assert SOAuth.header('http://lonelyvegan.com/', OAUTH_REQ_PARAMS)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Only certain {signature methods}[http://oauth.net/core/1.0/#signing_process] are supported
|
|
51
|
+
def test_unsupported_signature_method
|
|
52
|
+
oauth_params = OAUTH_REQ_PARAMS.merge(:signature_method => "MD5")
|
|
53
|
+
assert_raises(SOAuth::UnsupportedSignatureMethod) { SOAuth.header('http://lonelyvegan.com/', oauth_params) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
56
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: soauth
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: "0.
|
|
4
|
+
version: "0.2"
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthew Riley MacPherson
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2010-01-
|
|
12
|
+
date: 2010-01-06 00:00:00 -04:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
@@ -44,7 +44,7 @@ licenses: []
|
|
|
44
44
|
post_install_message:
|
|
45
45
|
rdoc_options:
|
|
46
46
|
- --title
|
|
47
|
-
- SOAuth -- Ruby
|
|
47
|
+
- SOAuth -- Ruby library that creates HTTP headers for OAuth Authorization
|
|
48
48
|
- --main
|
|
49
49
|
- README.markdown
|
|
50
50
|
- --line-numbers
|
|
@@ -68,6 +68,6 @@ rubyforge_project:
|
|
|
68
68
|
rubygems_version: 1.3.5
|
|
69
69
|
signing_key:
|
|
70
70
|
specification_version: 3
|
|
71
|
-
summary:
|
|
71
|
+
summary: Ruby library that creates HTTP headers for OAuth Authorization using previously-obtained OAuth keys/secrets
|
|
72
72
|
test_files:
|
|
73
73
|
- test/soauth_test.rb
|