diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 0fcd2bc..8e24794 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -9,7 +9,17 @@ def new url << "&oauth_callback=#{CGI.escape(TwitterAuth.oauth_callback)}" if TwitterAuth.oauth_callback? redirect_to url else - + # we don't have to do anything, it's just a simple form for HTTP basic! + end + end + + def create + logout_keeping_session! + if user = User.authenticate(params[:login], params[:password]) + self.current_user = user + authentication_succeeded and return + else + authentication_failed('Unable to verify your credentials through Twitter. Please try again.', '/login') and return end end diff --git a/app/models/twitter_auth/basic_user.rb b/app/models/twitter_auth/basic_user.rb index adb4623..f64ecd4 100644 --- a/app/models/twitter_auth/basic_user.rb +++ b/app/models/twitter_auth/basic_user.rb @@ -1,9 +1,62 @@ +require 'net/http' + module TwitterAuth module BasicUser def self.included(base) base.class_eval do attr_protected :crypted_password, :salt end + + base.extend TwitterAuth::BasicUser::ClassMethods + end + + module ClassMethods + def authenticate(login, password) + Twitter::Base.new(login, password).verify_credentials + + user = find_by_login(login) + rescue Twitter::CantConnect + nil + end + + def verify_credentials(login, password) + uri = URI.parse(TwitterAuth.base_url) + net = Net::HTTP.new(uri.host, uri.port) + net.use_ssl = TwitterAuth.base_url.match(/\Ahttps/) + response = net.start { |http| + request = Net::HTTP::Get.new('/account/verify_credentials.json') + request.basic_auth login, password + http.request(request) + } + if response.code == '200' + JSON.parse(response.body) + else + false + end + end + + def authorize(login, password) + if twitter_hash = verify_credentials(login, password) + user = identify_or_create_from_twitter_hash_and_password(twitter_hash, password) + user + else + nil + end + end + + def identify_or_create_from_twitter_hash_and_password(twitter_hash, password) + if user = User.find_by_login(twitter_hash['screen_name']) + user.assign_twitter_attributes(twitter_hash) + user.password = password + user.save + user + else + user = User.new_from_twitter_hash(twitter_hash) + user.password = password + user.save + user + end + end end def password=(new_password) diff --git a/app/views/sessions/_login_form.html.erb b/app/views/sessions/_login_form.html.erb new file mode 100644 index 0000000..d56c07a --- /dev/null +++ b/app/views/sessions/_login_form.html.erb @@ -0,0 +1,17 @@ +<% form_tag session_path, :id => 'login_form' do %> +
+ + <%= text_field_tag 'login', nil, :class => 'text_field' %> +
+
+ + <%= password_field_tag 'password', nil, :class => 'password_field' %> +
+ +
+ <%= submit_tag 'Log In', :class => 'submit' %> +
+<% end %> + diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb new file mode 100644 index 0000000..262927d --- /dev/null +++ b/app/views/sessions/new.html.erb @@ -0,0 +1,5 @@ +

Log In Via Twitter

+ +

This application utilizes your Twitter username and password for authentication; you do not have to create a separate account here. To log in, just enter your Twitter credentials in the form below.

+ +<%= render :partial => 'login_form' %> diff --git a/generators/twitter_auth/templates/user.rb b/generators/twitter_auth/templates/user.rb index 1ba3b65..79ba955 100644 --- a/generators/twitter_auth/templates/user.rb +++ b/generators/twitter_auth/templates/user.rb @@ -1,5 +1,5 @@ class User < TwitterAuth::GenericUser # Extend and define your user model as you see fit. # All of the authentication logic is handled by the - # parent TwitterAuth user class. + # parent TwitterAuth::GenericUser class. end diff --git a/lib/twitter_auth/controller_extensions.rb b/lib/twitter_auth/controller_extensions.rb index 3854a7e..1e6b6e5 100644 --- a/lib/twitter_auth/controller_extensions.rb +++ b/lib/twitter_auth/controller_extensions.rb @@ -9,20 +9,25 @@ def self.included(base) protected - def authentication_failed(message) + def authentication_failed(message, destination='/') flash[:error] = message - redirect_to '/' + redirect_to destination end - def authentication_succeeded(message = 'You have logged in successfully.') + def authentication_succeeded(message = 'You have logged in successfully.', destination = '/') flash[:notice] = message - redirect_to '/' + redirect_to destination end def current_user @current_user ||= User.find_by_id(session[:user_id]) end + def current_user=(new_user) + session[:user_id] = new_user.id + @current_user = new_user + end + def authorized? !!current_user end diff --git a/rails/init.rb b/rails/init.rb index ab1cf21..7a91a45 100644 --- a/rails/init.rb +++ b/rails/init.rb @@ -1,7 +1,7 @@ # Gem Dependencies config.gem 'oauth' config.gem 'ezcrypto' -config.gem 'httparty' +config.gem 'twitter' require 'json' require 'twitter_auth' diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 03c5c57..2df499e 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -1,6 +1,8 @@ require File.dirname(__FILE__) + '/../spec_helper' describe SessionsController do + integrate_views + describe 'routes' do it 'should route /session/new to SessionsController#new' do params_from(:get, '/session/new').should == {:controller => 'sessions', :action => 'new'} @@ -21,6 +23,10 @@ it 'should route /oauth_callback to SessionsController#oauth_callback' do params_from(:get, '/oauth_callback').should == {:controller => 'sessions', :action => 'oauth_callback'} end + + it 'should route POST /session to SessionsController#create' do + params_from(:post, '/session').should == {:controller => 'sessions', :action => 'create'} + end end describe 'with OAuth strategy' do @@ -138,6 +144,54 @@ end end + describe 'with Basic strategy' do + before do + stub_basic! + end + + describe '#new' do + it 'should render the new action' do + get :new + response.should render_template('sessions/new') + end + + it 'should render the login form' do + get :new + response.should have_tag('form[action=/session][id=login_form][method=post]') + end + + describe '#create' do + before do + @user = Factory.create(:twitter_basic_user) + end + + it 'should call logout_keeping_session! to remove session info' do + controller.should_receive(:logout_keeping_session!) + post :create + end + + it 'should try to authenticate the user' do + User.should_receive(:authenticate) + post :create + end + + it 'should call authentication_failed on authenticate failure' do + User.should_receive(:authenticate).and_return(nil) + post :create, :login => 'wrong', :password => 'false' + response.should redirect_to('/login') + end + + it 'should call authentication_succeeded on authentication success' do + User.should_receive(:authenticate).and_return(@user) + post :create, :login => 'twitterman', :password => 'cool' + response.should redirect_to('/') + flash[:notice].should_not be_blank + end + end + end + + end + describe '#destroy' do it 'should call logout_keeping_session!' do controller.should_receive(:logout_keeping_session!).once diff --git a/spec/fixtures/factories.rb b/spec/fixtures/factories.rb index e554474..a11f9d0 100644 --- a/spec/fixtures/factories.rb +++ b/spec/fixtures/factories.rb @@ -10,9 +10,9 @@ end Factory.define(:twitter_basic_user, :class => User) do |u| - u.login 'tweetkid' + u.login 'twitterman' u.password 'test' - u.name 'Tweet Kid' - u.description 'Twitter Man\'s trusty sidekick.' + u.name 'Twitter Man' + u.description 'Saving the world for all Twitter kind.' end diff --git a/spec/models/twitter_auth/basic_user_spec.rb b/spec/models/twitter_auth/basic_user_spec.rb index b5bfc82..3917bac 100644 --- a/spec/models/twitter_auth/basic_user_spec.rb +++ b/spec/models/twitter_auth/basic_user_spec.rb @@ -37,4 +37,72 @@ @user['password'].should_not == 'monkey' end end + + describe '.verify_credentials' do + before do + @user = Factory.create(:twitter_basic_user) + end + + it 'should return a JSON hash of the user when successful' do + hash = User.verify_credentials('twitterman','test') + hash.should be_a(Hash) + hash['screen_name'].should == 'twitterman' + hash['name'].should == 'Twitter Man' + end + + it 'should return false when a 401 unauthorized happens' do + FakeWeb.register_uri(:get, 'https://twitter.com:443/account/verify_credentials.json', :string => '401 "Unauthorized"', :status => ['401',' Unauthorized']) + User.verify_credentials('twitterman','wrong').should be_false + end + end + + describe '.authorize' do + before do + @user = Factory.create(:twitter_basic_user) + end + + it 'should make a call to verify_credentials' do + User.should_receive(:verify_credentials).with('twitterman','test') + User.authorize('twitterman','test') + end + + it 'should return nil if verify_credentials returns false' do + User.stub!(:verify_credentials).and_return(false) + User.authorize('twitterman','test').should be_nil + end + + it 'should return the user if verify_credentials succeeds' do + User.stub!(:verify_credentials).and_return(JSON.parse("{\"profile_image_url\":\"http:\\/\\/static.twitter.com\\/images\\/default_profile_normal.png\",\"description\":\"Saving the world for all Twitter kind.\",\"utc_offset\":null,\"favourites_count\":0,\"profile_sidebar_fill_color\":\"e0ff92\",\"screen_name\":\"twitterman\",\"statuses_count\":0,\"profile_background_tile\":false,\"profile_sidebar_border_color\":\"87bc44\",\"friends_count\":2,\"url\":null,\"name\":\"Twitter Man\",\"time_zone\":null,\"protected\":false,\"profile_background_image_url\":\"http:\\/\\/static.twitter.com\\/images\\/themes\\/theme1\\/bg.gif\",\"profile_background_color\":\"9ae4e8\",\"created_at\":\"Fri Feb 06 18:10:32 +0000 2009\",\"profile_text_color\":\"000000\",\"followers_count\":2,\"location\":null,\"id\":20256865,\"profile_link_color\":\"0000ff\"}")) + User.authorize('twitterman','test').should == @user + end + end + + describe '.find_or_create_by_twitter_hash_and_password' do + before do + @user = Factory.create(:twitter_basic_user) + end + + it 'should return the existing user if there is one' do + User.identify_or_create_from_twitter_hash_and_password({'screen_name' => 'twitterman'},'test').should == @user + end + + it 'should update the attributes from the hash' do + User.identify_or_create_from_twitter_hash_and_password({'screen_name' => 'twitterman', 'name' => 'New Name'}, 'test').name.should == 'New Name' + end + + it 'should update the password from the argument' do + User.identify_or_create_from_twitter_hash_and_password({'screen_name' => 'twitterman', 'name' => 'New Name'}, 'test2').password.should == 'test2' + end + + it 'should create a user if one does not exist' do + lambda{User.identify_or_create_from_twitter_hash_and_password({'screen_name' => 'dude', 'name' => "Lebowski"}, 'test')}.should change(User, :count).by(1) + end + + it 'should assign the attributes from the hash to a created user' do + user = User.identify_or_create_from_twitter_hash_and_password({'screen_name' => 'dude', 'name' => "Lebowski"}, 'test') + user.login.should == 'dude' + user.name.should == 'Lebowski' + user.password.should == 'test' + end + end end