Permalink
Browse files

Initial import.

  • Loading branch information...
1 parent 311d9f4 commit ac79e83a4fdd803eff6965acae0280a50f64562a Michael Bleigh committed Apr 22, 2010
View
@@ -1,3 +1,6 @@
+.rvmrc
+/live
+
## MAC OS
.DS_Store
View
@@ -1,6 +1,50 @@
-= oauth2
+= OAuth2
-Description goes here.
+A Ruby wrapper for the OAuth 2.0 specification. This is a work in progress, being built first to solve the pragmatic process of connecting to existing OAuth 2.0 endpoints (a.k.a. Facebook) with the goal of building it up to meet the entire specification over time.
+
+== Installation
+
+ gem install oauth2
+
+== Web Server Example (Sinatra)
+
+Below is a fully functional example of a Sinatra application that would authenticate to Facebook utilizing the OAuth 2.0 web server flow.
+
+ require 'rubygems'
+ require 'sinatra'
+ require 'oauth2'
+ require 'json'
+
+ def client
+ OAuth2::Client.new('5a816feeea97e6c7188a12f8e98a2c0f', 'd04fafd7d173b0e80853adab158fbd1f', :site => 'https://graph.facebook.com')
+ end
+
+ get '/auth/facebook' do
+ redirect client.web_server.authorize_url(
+ :redirect_uri => redirect_uri,
+ :scope => 'email,offline_access'
+ )
+ end
+
+ get '/auth/facebook/callback' do
+ begin
+ access_token = client.web_server.access_token(params[:code], :redirect_uri => redirect_uri)
+ user = JSON.parse(access_token.get('/me'))
+ rescue OAuth2::ErrorWithResponse => e
+ raise e.response.body
+ end
+
+ user.inspect
+ end
+
+ def redirect_uri
+ uri = URI.parse(request.url)
+ uri.path = '/auth/facebook/callback'
+ uri.query = nil
+ uri.to_s
+ end
+
+That's all there is to it! You can use the access token like you would with the OAuth gem, calling HTTP verbs on it etc.
== Note on Patches/Pull Requests
View
@@ -0,0 +1,11 @@
+module OAuth2
+ class ErrorWithResponse < StandardError; attr_accessor :response end
+ class AccessDenied < ErrorWithResponse; end
+ class HTTPError < ErrorWithResponse; end
+end
+
+require 'oauth2/uri'
+require 'oauth2/client'
+require 'oauth2/strategy/base'
+require 'oauth2/strategy/web_server'
+require 'oauth2/access_token'
View
@@ -0,0 +1,28 @@
+module OAuth2
+ class AccessToken
+ def initialize(client, token)
+ @client = client
+ @token = token
+ end
+
+ def request(verb, path, params = {}, headers = {})
+ @client.request(verb, path, params.merge('access_token' => @token), headers)
+ end
+
+ def get(path, params = {}, headers = {})
+ request(:get, path, params, headers)
+ end
+
+ def post(path, params = {}, headers = {})
+ request(:post, path, params, headers)
+ end
+
+ def put(path, params = {}, headers = {})
+ request(:put, path, params, headers)
+ end
+
+ def delete(path, params = {}, headers = {})
+ request(:delete, path, params, headers)
+ end
+ end
+end
View
@@ -0,0 +1,84 @@
+require 'net/https'
+
+module OAuth2
+ class Client
+ attr_accessor :id, :secret, :site, :options
+
+ # Instantiate a new OAuth 2.0 client using the
+ # client ID and client secret registered to your
+ # application.
+ #
+ # Options:
+ #
+ # <tt>:site</tt> :: Specify a base URL for your OAuth 2.0 client.
+ # <tt>:authorize_path</tt> :: Specify the path to the authorization endpoint.
+ # <tt>:authorize_url</tt> :: Specify a full URL of the authorization endpoint.
+ # <tt>:access_token_path</tt> :: Specify the path to the access token endpoint.
+ # <tt>:access_token_url</tt> :: Specify the full URL of the access token endpoint.
+ def initialize(client_id, client_secret, opts = {})
+ self.id = client_id
+ self.secret = client_secret
+ self.site = opts.delete(:site) if opts[:site]
+ self.options = opts
+ end
+
+ def authorize_url
+ return options[:authorize_url] if options[:authorize_url]
+
+ uri = URI.parse(site)
+ uri.path = options[:authorize_path] || "/oauth/authorize"
+ uri.to_s
+ end
+
+ def access_token_url
+ return options[:access_token_url] if options[:access_token_url]
+
+ uri = URI.parse(site)
+ uri.path = options[:access_token_path] || "/oauth/access_token"
+ uri.to_s
+ end
+
+ def request(verb, url_or_path, params = {}, headers = {})
+ if url_or_path[0..3] == 'http'
+ uri = URI.parse(url_or_path)
+ path = uri.path
+ else
+ uri = URI.parse(self.site)
+ path = (uri.path + url_or_path).gsub('//','/')
+ end
+
+ net = Net::HTTP.new(uri.host, uri.port)
+ net.use_ssl = (uri.scheme == 'https')
+
+ net.start do |http|
+ if verb == :get
+ uri.query_hash = uri.query_hash.merge(params)
+ path += "?#{uri.query}"
+ end
+
+ req = Net::HTTP.const_get(verb.to_s.capitalize).new(path, headers)
+
+ unless verb == :get
+ req.set_form_data(params)
+ end
+
+ response = http.request(req)
+
+ case response
+ when Net::HTTPSuccess
+ response.body
+ when Net::HTTPUnauthorized
+ e = OAuth2::AccessDenied.new("Received HTTP 401 when retrieving access token.")
+ e.response = response
+ raise e
+ else
+ e = OAuth2::HTTPError.new("Received HTTP #{response.code} when retrieving access token.")
+ e.response = response
+ raise e
+ end
+ end
+ end
+
+ def web_server; OAuth2::Strategy::WebServer.new(self) end
+ end
+end
@@ -0,0 +1,33 @@
+module OAuth2
+ module Strategy
+ class Base #:nodoc:
+ def initialize(client)#:nodoc:
+ @client = client
+ end
+
+ def authorize_url(options = {}) #:nodoc:
+ uri = URI.parse(@client.authorize_url)
+ uri.query_hash = authorize_params(options)
+ uri.to_s
+ end
+
+ def authorize_params(options = {}) #:nodoc:
+ options = options.inject({}){|h,(k,v)| h[k.to_s] = v; h}
+ {'client_id' => @client.id}.merge(options)
+ end
+
+ def access_token_url(options = {})
+ uri = URI.parse(@client.access_token_url)
+ uri.query_hash = access_token_params(options)
+ uri.to_s
+ end
+
+ def access_token_params(options = {})
+ {
+ 'client_id' => @client.id,
+ 'client_secret' => @client.secret
+ }.merge(options)
+ end
+ end
+ end
+end
@@ -0,0 +1,22 @@
+module OAuth2
+ module Strategy
+ class WebServer < Base
+ def authorize_params(options = {}) #:nodoc:
+ super(options).merge('type' => 'web_server')
+ end
+
+ def access_token(code, options = {})
+ response = @client.request(:get, @client.access_token_url, access_token_params(code, options))
+ token = response.split('&').inject({}){|h,kv| (k,v) = kv.split('='); h[k] = v; h}['access_token']
+ OAuth2::AccessToken.new(@client, token)
+ end
+
+ def access_token_params(code, options = {})
+ super(options).merge({
+ 'type' => 'web_server',
+ 'code' => code
+ })
+ end
+ end
+ end
+end
View
@@ -0,0 +1,14 @@
+require 'uri'
+require 'cgi'
+
+module URI
+ class Generic
+ def query_hash
+ CGI.parse(self.query || '').inject({}){|hash, (k,v)| hash[k] = (v.size == 1 ? v.first : v); hash}
+ end
+
+ def query_hash=(hash)
+ self.query = hash.map{|(k,v)| "#{k}=#{CGI.escape(v)}"}.join('&')
+ end
+ end
+end
View
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe OAuth2::Client do
+ subject{ OAuth2::Client.new('abc','def', :site => 'https://api.example.com')}
+
+ describe '#initialize' do
+ it 'should assign id and secret' do
+ subject.id.should == 'abc'
+ subject.secret.should == 'def'
+ end
+
+ it 'should assign site from the options hash' do
+ subject.site.should == 'https://api.example.com'
+ end
+ end
+
+ %w(authorize access_token).each do |path_type|
+ describe "##{path_type}_url" do
+ it "should default to a path of /oauth/#{path_type}" do
+ subject.send("#{path_type}_url").should == "https://api.example.com/oauth/#{path_type}"
+ end
+
+ it "should be settable via the :#{path_type}_path option" do
+ subject.options[:"#{path_type}_path"] = '/oauth/custom'
+ subject.send("#{path_type}_url").should == 'https://api.example.com/oauth/custom'
+ end
+
+ it "should be settable via the :#{path_type}_url option" do
+ subject.options[:"#{path_type}_url"] = 'https://abc.com/authorize'
+ subject.send("#{path_type}_url").should == 'https://abc.com/authorize'
+ end
+ end
+ end
+
+ it '#web_server should instantiate a WebServer strategy with this client' do
+ subject.web_server.should be_kind_of(OAuth2::Strategy::WebServer)
+ end
+end
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+describe OAuth2::Strategy::Base do
+ it 'should initialize with a Client' do
+ lambda{ OAuth2::Strategy::Base.new(OAuth2::Client.new('abc','def')) }.should_not raise_error
+ end
+end
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe OAuth2::Strategy::WebServer do
+ let(:client){ OAuth2::Client.new('abc','def', :site => 'http://api.example.com') }
+ subject { client.web_server }
+ describe '#authorize_url' do
+ it 'should include the client_id' do
+ subject.authorize_url.should be_include('client_id=abc')
+ end
+
+ it 'should include the type' do
+ subject.authorize_url.should be_include('type=web_server')
+ end
+
+ it 'should include passed in options' do
+ cb = 'http://myserver.local/oauth/callback'
+ subject.authorize_url(:redirect_uri => cb).should be_include("redirect_uri=#{CGI.escape(cb)}")
+ end
+ end
+end
View
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe URI::Generic do
+ subject{ URI.parse('http://example.com')}
+
+ describe '#query_hash' do
+ it 'should be a hash of the query parameters' do
+ subject.query_hash.should == {}
+ subject.query = 'abc=def&foo=123'
+ subject.query_hash.should == {'abc' => 'def', 'foo' => '123'}
+ end
+ end
+
+ describe '#query_hash=' do
+ it 'should set the query' do
+ subject.query_hash = {'abc' => 'def'}
+ subject.query.should == 'abc=def'
+ subject.query_hash = {'abc' => 'foo', 'bar' => 'baz'}
+ subject.query.should be_include('abc=foo')
+ subject.query.should be_include('bar=baz')
+ subject.query.split('&').size.should == 2
+ end
+
+ it 'should escape stuff' do
+ subject.query_hash = {'abc' => '$%!!'}
+ subject.query.should == "abc=#{CGI.escape('$%!!')}"
+ end
+ end
+end
View
@@ -1,7 +0,0 @@
-require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
-
-describe "Oauth2" do
- it "fails" do
- fail "hey buddy, you should probably rename this file and start specing for real"
- end
-end
View
@@ -1 +1,2 @@
--color
+--backtrace
Oops, something went wrong.

0 comments on commit ac79e83

Please sign in to comment.