Skip to content

Commit

Permalink
We're moving right along here...OAuth is essentially complete. Seriou…
Browse files Browse the repository at this point in the history
…sly.
  • Loading branch information
Michael Bleigh committed Mar 18, 2009
1 parent fa9b42c commit c34d3f4
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 15 deletions.
6 changes: 4 additions & 2 deletions README.markdown
Expand Up @@ -37,7 +37,9 @@ TwitterAuth provides some default controller methods that may be overridden in y
* `authorization_failed(message)`: called when Twitter authorization has failed during the process. By default, simply redirects to the site root.
* `authorization_succeeded`: called when Twitter authorization has completed successfully. By default, simply redirects to the site root.

Copyright
---------

**TwitterAuth** is Copyright (c) 2009 [Michael Bleigh](http://www.mbleigh.com) and [Intridea, Inc.](http://www.intridea.com/), released under the MIT License.


Copyright (c) 2009 [Michael Bleigh](http://www.mbleigh.com) and [Intridea, Inc.](http://www.intridea.com/), released under the MIT License
TwitterAuth is not affiliated with Twitter, Inc.
12 changes: 12 additions & 0 deletions app/controllers/sessions_controller.rb
Expand Up @@ -20,11 +20,23 @@ def oauth_callback
@request_token = OAuth::RequestToken.new(TwitterAuth.consumer, session[:request_token], session[:request_token_secret])

@access_token = @request_token.get_access_token

# The request token has been invalidated
# so we nullify it in the session.
session[:request_token] = nil
session[:request_token_secret] = nil

@user = User.identify_or_create_from_access_token(@access_token)

session[:user_id] = @user.id

authentication_succeeded
rescue Net::HTTPServerException => e
case e.message
when '401 "Unauthorized"'
authentication_failed('This authentication request is no longer valid. Please try again.') and return
else
authentication_failed('There was a problem trying to authenticate you. Please try again.') and return
end
end
end
5 changes: 4 additions & 1 deletion app/models/twitter_auth/generic_user.rb
@@ -1,5 +1,7 @@
module TwitterAuth
class GenericUser < ActiveRecord::Base
attr_protected :login

TWITTER_ATTRIBUTES = [
:name,
:location,
Expand Down Expand Up @@ -30,7 +32,8 @@ def self.table_name; 'users' end
def self.new_from_twitter_hash(hash)
raise ArgumentError, 'Invalid hash: must include screen_name.' unless hash.key?('screen_name')

user = User.new(:login => hash.delete('screen_name'))
user = User.new
user.login = hash['screen_name']

TWITTER_ATTRIBUTES.each do |att|
user.send("#{att}=", hash[att.to_s]) if user.respond_to?("#{att}=")
Expand Down
2 changes: 2 additions & 0 deletions app/models/twitter_auth/oauth_user.rb
@@ -1,5 +1,7 @@
module TwitterAuth
class OauthUser < TwitterAuth::GenericUser
attr_protected :access_token, :access_secret

def self.identify_or_create_from_access_token(token, secret=nil)
raise ArgumentError, 'Must authenticate with an OAuth::AccessToken or the string access token and secret.' unless (token && secret) || token.is_a?(OAuth::AccessToken)

Expand Down
12 changes: 12 additions & 0 deletions generators/twitter_auth/USAGE
@@ -0,0 +1,12 @@
TwitterAuth Generator
=====================

The TwitterAuth generator allows you to generate the components necessary to implement Twitter as a Single Sign-On provider for your site.

To run it, you simply need to call it:

script/generate twitter_auth

This will generate the migration necessary for the users table as well as generate a User model that extends the appropriate TwitterAuth model template and a config/twitter.yml that allows you to set your OAuth consumer key and secret.

By default, TwitterAuth uses OAuth as its authentication strategy. If you wish to use HTTP Basic you can pass in the --basic option.
6 changes: 3 additions & 3 deletions generators/twitter_auth/templates/migration.rb
@@ -1,11 +1,11 @@
class TwitterAuth < ActiveRecord::Migration
class TwitterAuthMigration < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :login
<% if options[:strategy] == 'basic' %>
<% if options[:oauth] %>
t.string :access_token
t.string :access_secret
<% else %>
<% elsif options[:basic] %>
t.string :crypted_password
t.string :salt
<% end %>
Expand Down
23 changes: 23 additions & 0 deletions generators/twitter_auth/templates/twitter_auth.yml
@@ -0,0 +1,23 @@
<% if options[:oauth] -%>
development:
strategy: oauth
oauth_consumer_key: devkey
oauth_consumer_secret: devsecret
oauth_callback: "http://localhost:3000/oauth_callback"
test:
strategy: oauth
oauth_consumer_key: testkey
oauth_consumer_secret: testsecret
oauth_callback: "http://localhost:3000/oauth_callback"
production:
strategy: oauth
oauth_consumer_key: prodkey
oauth_consumer_secret: prodsecret
<% else -%>
development:
strategy: basic
test:
strategy: basic
production:
strategy: basic
<% end %>
5 changes: 5 additions & 0 deletions generators/twitter_auth/templates/user.rb
@@ -0,0 +1,5 @@
class User < TwitterAuth::<%= options[:oauth] ? "Oauth" : "Basic" %>User
# Extend and define your user model as you see fit.
# All of the authentication logic is handled by the
# parent TwitterAuth user class.
end
33 changes: 33 additions & 0 deletions generators/twitter_auth/twitter_auth_generator.rb
@@ -0,0 +1,33 @@
class TwitterAuthGenerator < Rails::Generator::Base
default_options :oauth => true, :basic => false

def manifest
record do |m|
m.class_collisions 'User'

m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => 'twitter_auth_migration'
m.template 'user.rb', File.join('app','models','user.rb')
end
end

protected

def banner
"Usage: #{$0} twitter_auth"
end

def add_options!(opt)
opt.separator ''
opt.separator 'Options:'

opt.on('-O', '--oauth', 'Use the OAuth authentication strategy to connect to Twitter. (default)') { |v|
options[:oauth] = v
options[:basic] = !v
}

opt.on('-B', '--basic', 'Use the HTTP Basic authentication strategy to connect to Twitter.') { |v|
options[:basic] = v
options[:oauth] = !v
}
end
end
2 changes: 1 addition & 1 deletion lib/twitter_auth.rb
Expand Up @@ -3,7 +3,7 @@ module TwitterAuth
self.base_url = 'https://twitter.com'

def self.config(environment=RAILS_ENV)
YAML.load(File.open(RAILS_ROOT + '/config/twitter.yml').read)[environment]
YAML.load(File.open(RAILS_ROOT + '/config/twitter_auth.yml').read)[environment]
end

# The authentication strategy employed by this
Expand Down
40 changes: 39 additions & 1 deletion lib/twitter_auth/controller_extensions.rb
@@ -1,15 +1,53 @@
module TwitterAuth
# These methods borrow HEAVILY from Rick Olsen's
# Restful Authentication. All cleverness props
# go to him, not me.
module ControllerExtensions
def self.included(base)
base.send :helper_method, :current_user, :logged_in?, :authorized?
end

protected

def authentication_failed(message)
flash[:error] = message
redirect_to '/'
end

def authentication_succeeded
def authentication_succeeded(message = 'You have logged in successfully.')
flash[:notice] = message
redirect_to '/'
end

def current_user
@current_user ||= User.find_by_id(session[:user_id])
end

def authorized?
!!current_user
end

def login_required

end

def access_denied
store_location
redirect_to login_path
end

def store_location
session[:return_to] = request.request_uri
end

def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end

def logged_in?
!!current_user
end
end
end

Expand Down
2 changes: 2 additions & 0 deletions rails/init.rb
Expand Up @@ -3,3 +3,5 @@

require 'json'
require 'twitter_auth'

RAILS_DEFAULT_LOGGER.info("** TwitterAuth initialized properly.")
123 changes: 123 additions & 0 deletions spec/controllers/controller_extensions_spec.rb
@@ -0,0 +1,123 @@
require File.dirname(__FILE__) + '/../spec_helper'

ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
end

class TwitterAuthTestController < ApplicationController
before_filter :login_required, :only => [:login_required_action]

def login_required_action
render :text => "You are logged in!"
end

def fail_auth
authentication_failed('Auth FAIL.')
end

def pass_auth
if params[:message]
authentication_succeeded(params[:message])
else
authentication_succeeded
end
end

def access_denied_action
access_denied
end

def redirect_back_action
redirect_back_or_default(params[:to] || '/')
end
end

describe TwitterAuthTestController do
%w(authentication_failed authentication_succeeded current_user authorized? login_required access_denied store_location redirect_back_or_default).each do |m|
it "should respond to the extension method '#{m}'" do
controller.should respond_to(m)
end
end

describe "#authentication_failed" do
it 'should set the flash[:error] to the message passed in' do
get :fail_auth
flash[:error].should == 'Auth FAIL.'
end

it 'should redirect to the root' do
get :fail_auth
should redirect_to('/')
end
end

describe "#authentication_succeeded" do
it 'should set the flash[:notice] to a default success message' do
get :pass_auth
flash[:notice].should == 'You have logged in successfully.'
end

it 'should be able ot receive a custom message' do
get :pass_auth, :message => 'Eat at Joes.'
flash[:notice].should == 'Eat at Joes.'
end
end

describe '#current_user' do
it 'should find the user based on the session user_id' do
user = Factory.create(:twitter_oauth_user)
request.session[:user_id] = user.id
controller.send(:current_user).should == user
end

it 'should return nil if there is no user matching that id' do
request.session[:user_id] = 2345
controller.send(:current_user).should be_nil
end

it 'should memoize the result (and not do a double find)' do
user = Factory.create(:twitter_oauth_user)
User.should_receive(:find_by_id).once.and_return(user)
controller.send(:current_user).should == user
controller.send(:current_user).should == user
end
end

describe "#authorized?" do
it 'should be true if there is a current_user' do
user = Factory.create(:twitter_oauth_user)
controller.stub!(:current_user).and_return(user)
controller.send(:authorized?).should be_true
end

it 'should be false if there is not current_user' do
controller.stub!(:current_user).and_return(nil)
controller.send(:authorized?).should be_false
end
end

describe '#access_denied' do
it 'should redirect to the login path' do
get :access_denied_action
should redirect_to(login_path)
end

it 'should store the location first' do
controller.should_receive(:store_location).once
get :access_denied_action
end
end

describe '#redirect_back_or_default' do
it 'should redirect if there is a session[:return_to]' do
request.session[:return_to] = '/'
get :redirect_back_action, :to => '/notroot'
should redirect_to('/')
end

it 'should redirect to the default provided otherwise' do
get :redirect_back_action, :to => '/someurl'
should redirect_to('/someurl')
end
end
end

0 comments on commit c34d3f4

Please sign in to comment.