soauth 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1 +1,53 @@
1
- Use this library to create OAuth Authorization Headers 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.
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 (use Allison, because it's pretty)
3
+ # Make me some RDoc
4
4
  Rake::RDocTask.new do |rdoc|
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.template = ''
10
- rdoc.rdoc_dir = './doc'
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
@@ -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
- # 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?(:access_key) || !oauth.has_key?(:access_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
- # OAuth wants this to be "token"; change the hash key
37
- if k == :access_key
38
- sig_params['oauth_token'] = v
39
- # Only use certain OAuth values for the base string
40
- elsif [:consumer_key, :signature_method, :version, :nonce, :timestamp].include?(k)
41
- sig_params['oauth_' + k.to_s] = v
42
- end
43
- }
44
-
45
- secret = "#{escape(oauth[:consumer_secret])}&#{escape(oauth[:access_secret])}"
46
- sig_base = (http_method||'get').to_s.upcase + '&' + escape(uri) + '&' + normalize(sig_params)
47
- oauth_signature = Base64.encode64(OpenSSL::HMAC.digest(DIGEST, secret, sig_base)).chomp.gsub(/\n/,'')
48
-
49
- %{OAuth } + (%{oauth_realm="#{oauth[:realm]}", } unless !oauth[:realm]).to_s + %{oauth_consumer_key="#{oauth[:consumer_key]}", oauth_token="#{oauth[:access_key]}", oauth_signature_method="#{oauth[:signature_method]}", oauth_signature="#{escape(oauth_signature)}", oauth_timestamp="#{oauth[:timestamp]}", oauth_nonce="#{oauth[:nonce]}", oauth_version="#{oauth[:version]}"}
50
- end
51
-
52
- # Utility class used to sign a request and return an
53
- # {OAuth "Authorization" HTTP header}[http://oauth.net/core/1.0/#auth_header]
54
- def self.header(uri, oauth, params = {}, http_method = :get)
55
- new.header(uri, oauth, params, http_method)
56
- end
57
-
58
- protected
59
-
60
- # Escape characters in a string according to the {OAuth spec}[http://oauth.net/core/1.0/]
61
- def escape(value)
62
- URI::escape(value.to_s, /[^a-zA-Z0-9\-\.\_\~]/) # Unreserved characters -- must not be encoded
63
- end
64
-
65
- # Normalize a string of parameters based on the {OAuth spec}[http://oauth.net/core/1.0/#rfc.section.9.1.1]
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
@@ -2,15 +2,15 @@ require 'rake'
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "soauth"
5
- s.version = "0.1"
6
- #s.date = Time.now.to_s
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 Library that creates HTTP headers for OAuth Authorization" << '--main' << 'README.markdown' << '--line-numbers'
11
- s.summary = "Create OAuth \"Authorization\" HTTP Header using previously-obtained OAuth data"
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
- s.test_file = 'test/soauth_test.rb'
15
- s.add_development_dependency('mocha') # Used to run the tests, that's all...
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
@@ -6,51 +6,51 @@ require 'test/unit'
6
6
  require 'mocha'
7
7
 
8
8
  class SOAuthTest < Test::Unit::TestCase
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
- :access_key => "access_key",
17
- :access_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
-
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.1"
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-04 00:00:00 -04:00
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 Library that creates HTTP headers for OAuth Authorization
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: Create OAuth "Authorization" HTTP Header using previously-obtained OAuth data
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