├── VERSION ├── .gitignore ├── test ├── helper.rb └── test_roauth.rb ├── Rakefile ├── LICENSE ├── roauth.gemspec ├── README.markdown └── lib └── roauth.rb /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.8 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | *.gem 3 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'shoulda' 4 | 5 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 6 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 7 | require 'roauth' 8 | 9 | class Test::Unit::TestCase 10 | end 11 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | begin 5 | require 'jeweler' 6 | Jeweler::Tasks.new do |gemspec| 7 | gemspec.name = "roauth" 8 | gemspec.summary = "Simple Ruby OAuth library" 9 | gemspec.email = "info@eribium.org" 10 | gemspec.homepage = "http://github.com/maccman/roauth" 11 | gemspec.description = "Simple Ruby OAuth library" 12 | gemspec.authors = ["Alex MacCaw"] 13 | end 14 | rescue LoadError 15 | puts "Jeweler not available. Install it with: sudo gem install jeweler" 16 | end 17 | 18 | 19 | require 'rake/testtask' 20 | Rake::TestTask.new(:test) do |test| 21 | test.libs << 'lib' << 'test' 22 | test.pattern = 'test/**/test_*.rb' 23 | test.verbose = true 24 | end 25 | 26 | task :test => :check_dependencies 27 | task :default => :test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Alex MacCaw 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /roauth.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{roauth} 8 | s.version = "0.0.7" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Alex MacCaw"] 12 | s.date = %q{2011-06-09} 13 | s.description = %q{Simple Ruby OAuth library} 14 | s.email = %q{info@eribium.org} 15 | s.extra_rdoc_files = [ 16 | "LICENSE", 17 | "README.markdown" 18 | ] 19 | s.files = [ 20 | "LICENSE", 21 | "README.markdown", 22 | "Rakefile", 23 | "VERSION", 24 | "lib/roauth.rb", 25 | "roauth.gemspec", 26 | "test/helper.rb", 27 | "test/test_roauth.rb" 28 | ] 29 | s.homepage = %q{http://github.com/maccman/roauth} 30 | s.require_paths = ["lib"] 31 | s.rubygems_version = %q{1.6.0} 32 | s.summary = %q{Simple Ruby OAuth library} 33 | 34 | if s.respond_to? :specification_version then 35 | s.specification_version = 3 36 | 37 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 38 | else 39 | end 40 | else 41 | end 42 | end 43 | 44 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Based on SOAuth: http://github.com/tofumatt/SOAuth 2 | 3 | A *simple* OAuth library that supports OAuth header signing, and header verifying. 4 | 5 | gem install roauth 6 | 7 | Example Client: 8 | 9 | require "roauth" 10 | require "nestful" 11 | 12 | url = "https://twitter.com/direct_messages.json" 13 | 14 | oauth = { 15 | :consumer_key => "consumer_key", 16 | :consumer_secret => "consumer_secret", 17 | :access_key => "access_key", 18 | :access_secret => "access_secret" 19 | } 20 | 21 | params = { 22 | :count => "11", 23 | :since_id => "5000" 24 | } 25 | oauth_header = ROAuth.header(oauth, url, params) 26 | 27 | Nestful.get(url, :params => params, :headers => {'Authorization' => oauth_header}) 28 | 29 | Example Server: 30 | 31 | oauth_header = ROAuth.parse(request.header['Authorization']) 32 | 33 | # Implementation specific 34 | consumer = Consumer.find_by_key(oauth_header[:consumer_key]) 35 | access_token = AccessToken.find_by_token(oauth_header[:access_key]) 36 | oauth = { 37 | :consumer_secret => consumer.secret, 38 | :access_secret => access_token.secret 39 | } 40 | 41 | ROAuth.verify(oauth, oauth_header, request.request_uri, params) #=> true/false -------------------------------------------------------------------------------- /test/test_roauth.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestRoauth < Test::Unit::TestCase 4 | should "correctly sign params" do 5 | url = "https://twitter.com/direct_messages.json" 6 | 7 | oauth = { 8 | :consumer_key => "consumer_key", 9 | :consumer_secret => "consumer_secret", 10 | :access_key => "access_key", 11 | :access_secret => "access_secret", 12 | :nonce => "foo", 13 | :timestamp => 1286967499 14 | } 15 | 16 | params = { 17 | :count => "11", 18 | :since_id => "5000" 19 | } 20 | 21 | oauth_header = ROAuth.header(oauth, url, params) 22 | signature = ROAuth.parse(oauth_header)[:signature] 23 | 24 | assert_equal "9/g1ge6nLYVkBsTEqgxH0Xlv2O4=", signature 25 | end 26 | 27 | should "verify correctly signed params" do 28 | url = "https://twitter.com/direct_messages.json" 29 | 30 | oauth = { 31 | :consumer_key => "consumer_key", 32 | :consumer_secret => "consumer_secret", 33 | :access_key => "access_key", 34 | :access_secret => "access_secret", 35 | :nonce => "foo", 36 | :timestamp => 1286967499 37 | } 38 | 39 | params = { 40 | :count => "11", 41 | :since_id => "5000" 42 | } 43 | 44 | header = %{OAuth oauth_consumer_key="consumer_key", oauth_nonce="foo", oauth_signature="9%2Fg1ge6nLYVkBsTEqgxH0Xlv2O4%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1286967499", oauth_token="access_key", oauth_version="1.0"} 45 | assert ROAuth.verify(oauth, header, url, params), "verify failed" 46 | end 47 | end -------------------------------------------------------------------------------- /lib/roauth.rb: -------------------------------------------------------------------------------- 1 | require "base64" 2 | require "openssl" 3 | require "uri" 4 | 5 | module ROAuth 6 | class UnsupportedSignatureMethod < Exception; end 7 | class MissingOAuthParams < Exception; end 8 | 9 | # Supported {signature methods}[http://oauth.net/core/1.0/#signing_process]; 10 | SIGNATURE_METHODS = {"HMAC-SHA1" => OpenSSL::Digest::Digest.new("sha1")} 11 | OAUTH_PARAMS = [:consumer_key, :token, :signature_method, :version, :nonce, :timestamp, :body_hash, :callback] 12 | 13 | # Return an {OAuth "Authorization" HTTP header}[http://oauth.net/core/1.0/#auth_header] from request data 14 | def header(oauth, uri, params = {}, http_method = :get) 15 | oauth = oauth.dup 16 | oauth[:signature_method] ||= "HMAC-SHA1" 17 | oauth[:version] ||= "1.0" # Assumed version, according to the spec 18 | oauth[:nonce] ||= Base64.encode64(OpenSSL::Random.random_bytes(32)).gsub(/\W/, '') 19 | oauth[:timestamp] ||= Time.now.to_i 20 | oauth[:token] ||= oauth.delete(:access_key) 21 | oauth[:token_secret] ||= oauth.delete(:access_secret) 22 | 23 | sig_params = oauth_params(oauth) 24 | sig_params[:oauth_signature] = signature(oauth, uri, sig_params.merge(params), http_method) 25 | sorted_sig_params = sig_params.sort_by{|k,v| [k.to_s, v.to_s] } 26 | authorization_params = sorted_sig_params.map {|key, value| 27 | [escape(key), "\"#{escape(value)}\""].join("=") 28 | }.join(", ") 29 | 30 | %{OAuth } + authorization_params 31 | end 32 | 33 | def parse(header) 34 | header = header.dup 35 | header = header.gsub!(/^OAuth\s/, "") 36 | header = header.split(", ") 37 | header = header.inject({}) {|hash, item| 38 | key, value = item.split("=") 39 | key.gsub!(/^oauth_/, "") 40 | value.gsub!(/(^"|"$)/, "") 41 | hash[key.to_sym] = unescape(value) 42 | hash 43 | } 44 | header[:access_key] = header[:token] 45 | header 46 | end 47 | 48 | def verify(oauth, header, uri, params = {}, http_method = :get) 49 | header = header.is_a?(String) ? parse(header) : header.dup 50 | 51 | client_signature = header.delete(:signature) 52 | oauth[:consumer_key] ||= header[:consumer_key] 53 | oauth[:token] ||= header[:token] 54 | oauth[:token_secret] ||= oauth.delete(:access_secret) 55 | oauth[:signature_method] ||= "HMAC-SHA1" 56 | oauth[:version] ||= "1.0" 57 | 58 | sig_params = params.dup 59 | sig_params.merge!(oauth_params(header)) 60 | 61 | client_signature == signature(oauth, uri, sig_params, http_method) 62 | end 63 | 64 | protected 65 | def oauth_params(oauth) 66 | oauth = oauth.to_a.select {|key, value| 67 | OAUTH_PARAMS.include?(key) 68 | } 69 | oauth.inject({}) {|hash, (key, value)| 70 | hash["oauth_#{key}"] = value 71 | hash 72 | } 73 | end 74 | 75 | def signature(oauth, uri, params, http_method = :get) 76 | uri = URI.parse(uri) 77 | uri.query = nil 78 | uri = uri.to_s 79 | 80 | sig_base = http_method.to_s.upcase + "&" + escape(uri) + "&" + escape(normalize(params)) 81 | digest = SIGNATURE_METHODS[oauth[:signature_method]] 82 | secret = "#{escape(oauth[:consumer_secret])}&#{escape(oauth[:token_secret])}" 83 | 84 | Base64.encode64(OpenSSL::HMAC.digest(digest, secret, sig_base)).chomp.gsub(/\n/, "") 85 | end 86 | 87 | # Escape characters in a string according to the {OAuth spec}[http://oauth.net/core/1.0/] 88 | def escape(value) 89 | URI.escape(value.to_s, /[^a-zA-Z0-9\-\.\_\~]/) # Unreserved characters -- must not be encoded 90 | end 91 | 92 | def unescape(value) 93 | URI.unescape(value) 94 | end 95 | 96 | # Normalize a string of parameters based on the {OAuth spec}[http://oauth.net/core/1.0/#rfc.section.9.1.1] 97 | def normalize(params) 98 | # Stringify keys - so we can compare them 99 | params.keys.each {|key| params[key.to_s] = params.delete(key) } 100 | params.sort_by {|key, values| key.to_s }.map do |key, values| 101 | if values.is_a?(Array) 102 | # Multiple values were provided for a single key 103 | # in a hash 104 | values.sort_by(&:to_s).collect do |v| 105 | [escape(key), escape(v)] * "=" 106 | end 107 | else 108 | [escape(key), escape(values)] * "=" 109 | end 110 | end * "&" 111 | end 112 | extend self 113 | end --------------------------------------------------------------------------------