Permalink
Fetching contributors…
Cannot retrieve contributors at this time
168 lines (135 sloc) 4.56 KB
require 'oauth'
require 'yajl'
require 'rack/request'
require 'hashie/mash'
module Twitter
end
class Twitter::Login
attr_reader :options
class << self
attr_accessor :consumer_key, :secret, :site
end
DEFAULTS = {
:return_to => '/',
:site => 'http://api.twitter.com',
:authorize_path => '/oauth/authenticate'
}
def initialize(options)
@options = DEFAULTS.merge options
self.class.consumer_key, self.class.secret = @options[:consumer_key], @options[:secret]
self.class.site = @options[:site]
end
def login_handler(options = {})
@options.update options
return self
end
def call(env)
request = Request.new(env)
if request[:oauth_verifier]
# user authorized the app
handle_twitter_authorization(request)
elsif request[:denied]
# user refused to log in with Twitter
handle_denied_access(request)
else
# starting the login process; send user to Twitter
redirect_to_twitter(request)
end
end
module Helpers
def twitter_client
OAuth::AccessToken.new(twitter_oauth, *session[:twitter_access_token])
end
def twitter_oauth
OAuth::Consumer.new Twitter::Login.consumer_key, Twitter::Login.secret,
:site => Twitter::Login.site
end
def twitter_user
if session[:twitter_user]
Hashie::Mash[session[:twitter_user]]
end
end
def twitter_logout
[:twitter_access_token, :twitter_user].each do |key|
session[key] = nil # work around a Rails 2.3.5 bug
session.delete key
end
end
end
class Request < Rack::Request
# for storing :request_token, :access_token
def session
env['rack.session'] ||= {}
end
# SUCKS: must duplicate logic from the `url` method
def url_for(path)
url = scheme + '://' + host
if scheme == 'https' && port != 443 ||
scheme == 'http' && port != 80
url << ":#{port}"
end
url << path
end
def wants?(mime_type)
env['HTTP_ACCEPT'].to_s.include? mime_type
end
end
protected
def redirect_to_twitter(request)
# create a request token and store its parameter in session
request_token = oauth.get_request_token(:oauth_callback => request.url)
request.session[:twitter_request_token] = [request_token.token, request_token.secret]
# redirect to Twitter authorization page
if request.wants? 'application/json'
body = %({"authorize_url":"#{request_token.authorize_url}"})
["200", {'Content-type' => 'application/json'}, [body]]
elsif request.xhr? or request.wants? 'application/javascript'
body = "window.location.assign('#{request_token.authorize_url}')"
["200", {'Content-type' => 'application/javascript'}, [body]]
else
redirect request_token.authorize_url
end
end
def handle_twitter_authorization(request)
access_token = authorize_from_request(request)
# get and store authenticated user's info from Twitter
response = access_token.get('/1/account/verify_credentials.json')
request.session[:twitter_user] = user_hash_from_response(response)
redirect_to_return_path(request)
end
def handle_denied_access(request)
# cleanup session and set an error identifier
request.session[:twitter_request_token] = nil # work around a Rails 2.3.5 bug
request.session.delete(:twitter_request_token)
request.session[:twitter_error] = 'user_denied'
redirect_to_return_path(request)
end
private
# replace the request token in session with access token
def authorize_from_request(request)
rtoken, rsecret = request.session[:twitter_request_token]
request_token = OAuth::RequestToken.new(oauth, rtoken, rsecret)
request_token.get_access_token(:oauth_verifier => request[:oauth_verifier]).tap do |access_token|
request.session.delete(:twitter_request_token)
request.session[:twitter_access_token] = [access_token.token, access_token.secret]
end
end
def redirect_to_return_path(request)
redirect request.url_for(options[:return_to])
end
def redirect(url)
["302", {'Location' => url, 'Content-type' => 'text/plain'}, []]
end
def oauth
OAuth::Consumer.new options[:consumer_key], options[:secret],
:site => options[:site], :authorize_path => options[:authorize_path]
end
def user_hash_from_response(api_response)
parse_response(api_response).reject { |key, _|
key == 'status' or key =~ /^profile_|_color$/
}
end
def parse_response(api_response)
Yajl::Parser.parse api_response.body
end
end