changelog shortlog tags manifest raw

changeset: generate authenticated again.

changeset 7: ac1024130232
parent 6:ebbe8fce79fb
child 8:eddc0740bd25
author: moriq@moriq.com
date: Wed Mar 05 03:57:54 2008 +0900 (16 years ago)
files: app/controllers/application.rb app/controllers/users_controller.rb app/helpers/sessions_helper.rb app/helpers/users_helper.rb app/views/home/index.html.erb app/views/sessions/new.html.erb app/views/users/new.html.erb config/routes.rb db/migrate/001_create_users.rb lib/authenticated_system.rb lib/authenticated_test_helper.rb test/fixtures/users.yml test/functional/sessions_controller_test.rb test/functional/users_controller_test.rb test/unit/user_test.rb
description: generate authenticated again.
mercurial import したときに db/migrate lib が消えてた。orz
--- a/app/controllers/application.rb	Wed Mar 05 03:47:33 2008 +0900
+++ b/app/controllers/application.rb	Wed Mar 05 03:57:54 2008 +0900
@@ -4,6 +4,10 @@ class ApplicationController < ActionCont
 class ApplicationController < ActionController::Base
   helper :all # include all helpers, all the time
 
+  # Be sure to include AuthenticationSystem in Application Controller instead
+  include AuthenticatedSystem
+  
+
   # See ActionController::RequestForgeryProtection for details
   # Uncomment the :secret if you're not using the cookie session store
   protect_from_forgery # :secret => '34d199dc292649987bc6a6f4e7174608'
--- a/app/controllers/users_controller.rb	Wed Mar 05 03:47:33 2008 +0900
+++ b/app/controllers/users_controller.rb	Wed Mar 05 03:57:54 2008 +0900
@@ -1,8 +1,4 @@ class UsersController < ApplicationContr
 class UsersController < ApplicationController
-  # Be sure to include AuthenticationSystem in Application Controller instead
-  include AuthenticatedSystem
-  
-
   # render new.rhtml
   def new
   end
--- a/app/views/home/index.html.erb	Wed Mar 05 03:47:33 2008 +0900
+++ b/app/views/home/index.html.erb	Wed Mar 05 03:57:54 2008 +0900
@@ -1,2 +1,12 @@
-<h1>Home#index</h1>
-<p>Find me in app/views/home/index.html.erb</p>
+<h1>Welcome</h1>
+
+<%-if logged_in?-%>
+<p><strong>You are logged in as <%=h current_user.login %>.</strong></p>
+<p><%= link_to 'Logout', session_path, :method => :delete %></p>
+<%-else-%>
+<p><strong>You are currently not logged in.</strong></p>
+<p>
+  <%= link_to 'Login', new_session_path %> or
+  <%= link_to 'Sign Up', new_user_path %>
+</p>
+<%-end-%>
--- a/config/routes.rb	Wed Mar 05 03:47:33 2008 +0900
+++ b/config/routes.rb	Wed Mar 05 03:57:54 2008 +0900
@@ -1,4 +1,16 @@ ActionController::Routing::Routes.draw d
 ActionController::Routing::Routes.draw do |map|
+  map.resources :users
+
+  map.resource :session
+
+  map.resources :users
+
+  map.resource :session
+
+  map.resources :users
+
+  map.resource :session
+
   # The priority is based upon order of creation: first created -> highest priority.
 
   # Sample of regular route:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/helpers/sessions_helper.rb	Wed Mar 05 03:57:54 2008 +0900
@@ -0,0 +1,2 @@
+module SessionsHelper
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/helpers/users_helper.rb	Wed Mar 05 03:57:54 2008 +0900
@@ -0,0 +1,2 @@
+module UsersHelper
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/views/sessions/new.html.erb	Wed Mar 05 03:57:54 2008 +0900
@@ -0,0 +1,14 @@
+<% form_tag session_path do -%>
+<p><label for="login">Login</label><br/>
+<%= text_field_tag 'login' %></p>
+
+<p><label for="password">Password</label><br/>
+<%= password_field_tag 'password' %></p>
+
+<!-- Uncomment this if you want this functionality
+<p><label for="remember_me">Remember me:</label>
+<%= check_box_tag 'remember_me' %></p>
+-->
+
+<p><%= submit_tag 'Log in' %></p>
+<% end -%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/views/users/new.html.erb	Wed Mar 05 03:57:54 2008 +0900
@@ -0,0 +1,16 @@
+<%= error_messages_for :user %>
+<% form_for :user, :url => users_path do |f| -%>
+<p><label for="login">Login</label><br/>
+<%= f.text_field :login %></p>
+
+<p><label for="email">Email</label><br/>
+<%= f.text_field :email %></p>
+
+<p><label for="password">Password</label><br/>
+<%= f.password_field :password %></p>
+
+<p><label for="password_confirmation">Confirm Password</label><br/>
+<%= f.password_field :password_confirmation %></p>
+
+<p><%= submit_tag 'Sign up' %></p>
+<% end -%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/db/migrate/001_create_users.rb	Wed Mar 05 03:57:54 2008 +0900
@@ -0,0 +1,20 @@
+class CreateUsers < ActiveRecord::Migration
+  def self.up
+    create_table "users", :force => true do |t|
+      t.column :login,                     :string
+      t.column :email,                     :string
+      t.column :crypted_password,          :string, :limit => 40
+      t.column :salt,                      :string, :limit => 40
+      t.column :created_at,                :datetime
+      t.column :updated_at,                :datetime
+      t.column :remember_token,            :string
+      t.column :remember_token_expires_at, :datetime
+      
+      
+    end
+  end
+
+  def self.down
+    drop_table "users"
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/authenticated_system.rb	Wed Mar 05 03:57:54 2008 +0900
@@ -0,0 +1,116 @@
+module AuthenticatedSystem
+  protected
+    # Returns true or false if the user is logged in.
+    # Preloads @current_user with the user model if they're logged in.
+    def logged_in?
+      current_user != :false
+    end
+
+    # Accesses the current user from the session.  Set it to :false if login fails
+    # so that future calls do not hit the database.
+    def current_user
+      @current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie || :false)
+    end
+
+    # Store the given user id in the session.
+    def current_user=(new_user)
+      session[:user_id] = (new_user.nil? || new_user.is_a?(Symbol)) ? nil : new_user.id
+      @current_user = new_user || :false
+    end
+
+    # Check if the user is authorized
+    #
+    # Override this method in your controllers if you want to restrict access
+    # to only a few actions or if you want to check if the user
+    # has the correct rights.
+    #
+    # Example:
+    #
+    #  # only allow nonbobs
+    #  def authorized?
+    #    current_user.login != "bob"
+    #  end
+    def authorized?
+      logged_in?
+    end
+
+    # Filter method to enforce a login requirement.
+    #
+    # To require logins for all actions, use this in your controllers:
+    #
+    #   before_filter :login_required
+    #
+    # To require logins for specific actions, use this in your controllers:
+    #
+    #   before_filter :login_required, :only => [ :edit, :update ]
+    #
+    # To skip this in a subclassed controller:
+    #
+    #   skip_before_filter :login_required
+    #
+    def login_required
+      authorized? || access_denied
+    end
+
+    # Redirect as appropriate when an access request fails.
+    #
+    # The default action is to redirect to the login screen.
+    #
+    # Override this method in your controllers if you want to have special
+    # behavior in case the user is not authorized
+    # to access the requested action.  For example, a popup window might
+    # simply close itself.
+    def access_denied
+      respond_to do |format|
+        format.html do
+          store_location
+          redirect_to new_session_path
+        end
+        format.any do
+          request_http_basic_authentication 'Web Password'
+        end
+      end
+    end
+
+    # Store the URI of the current request in the session.
+    #
+    # We can return to this location by calling #redirect_back_or_default.
+    def store_location
+      session[:return_to] = request.request_uri
+    end
+
+    # Redirect to the URI stored by the most recent store_location call or
+    # to the passed default.
+    def redirect_back_or_default(default)
+      redirect_to(session[:return_to] || default)
+      session[:return_to] = nil
+    end
+
+    # Inclusion hook to make #current_user and #logged_in?
+    # available as ActionView helper methods.
+    def self.included(base)
+      base.send :helper_method, :current_user, :logged_in?
+    end
+
+    # Called from #current_user.  First attempt to login by the user id stored in the session.
+    def login_from_session
+      self.current_user = User.find_by_id(session[:user_id]) if session[:user_id]
+    end
+
+    # Called from #current_user.  Now, attempt to login by basic authentication information.
+    def login_from_basic_auth
+      authenticate_with_http_basic do |username, password|
+        self.current_user = User.authenticate(username, password)
+      end
+    end
+
+    # Called from #current_user.  Finaly, attempt to login by an expiring token in the cookie.
+    def login_from_cookie
+      user = cookies[:auth_token] && User.find_by_remember_token(cookies[:auth_token])
+      if user && user.remember_token?
+        user.remember_me
+        cookies[:auth_token] = { :value => user.remember_token, :expires => user.remember_token_expires_at }
+        self.current_user = user
+      end
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/authenticated_test_helper.rb	Wed Mar 05 03:57:54 2008 +0900
@@ -0,0 +1,10 @@
+module AuthenticatedTestHelper
+  # Sets the current user in the session from the user fixtures.
+  def login_as(user)
+    @request.session[:user_id] = user ? users(user).id : nil
+  end
+
+  def authorize_as(user)
+    @request.env["HTTP_AUTHORIZATION"] = user ? ActionController::HttpAuthentication::Basic.encode_credentials(users(user).login, 'test') : nil
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/fixtures/users.yml	Wed Mar 05 03:57:54 2008 +0900
@@ -0,0 +1,19 @@
+quentin:
+  id: 1
+  login: quentin
+  email: quentin@example.com
+  salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
+  crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
+  created_at: <%= 5.days.ago.to_s :db %>
+
+
+
+aaron:
+  id: 2
+  login: aaron
+  email: aaron@example.com
+  salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
+  crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
+  created_at: <%= 1.days.ago.to_s :db %>
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/functional/sessions_controller_test.rb	Wed Mar 05 03:57:54 2008 +0900
@@ -0,0 +1,85 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sessions_controller'
+
+# Re-raise errors caught by the controller.
+class SessionsController; def rescue_action(e) raise e end; end
+
+class SessionsControllerTest < Test::Unit::TestCase
+  # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead
+  # Then, you can remove it from this and the units test.
+  include AuthenticatedTestHelper
+
+  fixtures :users
+
+  def setup
+    @controller = SessionsController.new
+    @request    = ActionController::TestRequest.new
+    @response   = ActionController::TestResponse.new
+  end
+
+  def test_should_login_and_redirect
+    post :create, :login => 'quentin', :password => 'test'
+    assert session[:user_id]
+    assert_response :redirect
+  end
+
+  def test_should_fail_login_and_not_redirect
+    post :create, :login => 'quentin', :password => 'bad password'
+    assert_nil session[:user_id]
+    assert_response :success
+  end
+
+  def test_should_logout
+    login_as :quentin
+    get :destroy
+    assert_nil session[:user_id]
+    assert_response :redirect
+  end
+
+  def test_should_remember_me
+    post :create, :login => 'quentin', :password => 'test', :remember_me => "1"
+    assert_not_nil @response.cookies["auth_token"]
+  end
+
+  def test_should_not_remember_me
+    post :create, :login => 'quentin', :password => 'test', :remember_me => "0"
+    assert_nil @response.cookies["auth_token"]
+  end
+  
+  def test_should_delete_token_on_logout
+    login_as :quentin
+    get :destroy
+    assert_equal @response.cookies["auth_token"], []
+  end
+
+  def test_should_login_with_cookie
+    users(:quentin).remember_me
+    @request.cookies["auth_token"] = cookie_for(:quentin)
+    get :new
+    assert @controller.send(:logged_in?)
+  end
+
+  def test_should_fail_expired_cookie_login
+    users(:quentin).remember_me
+    users(:quentin).update_attribute :remember_token_expires_at, 5.minutes.ago
+    @request.cookies["auth_token"] = cookie_for(:quentin)
+    get :new
+    assert !@controller.send(:logged_in?)
+  end
+
+  def test_should_fail_cookie_login
+    users(:quentin).remember_me
+    @request.cookies["auth_token"] = auth_token('invalid_auth_token')
+    get :new
+    assert !@controller.send(:logged_in?)
+  end
+
+  protected
+    def auth_token(token)
+      CGI::Cookie.new('name' => 'auth_token', 'value' => token)
+    end
+    
+    def cookie_for(user)
+      auth_token users(user).remember_token
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/functional/users_controller_test.rb	Wed Mar 05 03:57:54 2008 +0900
@@ -0,0 +1,65 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'users_controller'
+
+# Re-raise errors caught by the controller.
+class UsersController; def rescue_action(e) raise e end; end
+
+class UsersControllerTest < Test::Unit::TestCase
+  # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead
+  # Then, you can remove it from this and the units test.
+  include AuthenticatedTestHelper
+
+  fixtures :users
+
+  def setup
+    @controller = UsersController.new
+    @request    = ActionController::TestRequest.new
+    @response   = ActionController::TestResponse.new
+  end
+
+  def test_should_allow_signup
+    assert_difference 'User.count' do
+      create_user
+      assert_response :redirect
+    end
+  end
+
+  def test_should_require_login_on_signup
+    assert_no_difference 'User.count' do
+      create_user(:login => nil)
+      assert assigns(:user).errors.on(:login)
+      assert_response :success
+    end
+  end
+
+  def test_should_require_password_on_signup
+    assert_no_difference 'User.count' do
+      create_user(:password => nil)
+      assert assigns(:user).errors.on(:password)
+      assert_response :success
+    end
+  end
+
+  def test_should_require_password_confirmation_on_signup
+    assert_no_difference 'User.count' do
+      create_user(:password_confirmation => nil)
+      assert assigns(:user).errors.on(:password_confirmation)
+      assert_response :success
+    end
+  end
+
+  def test_should_require_email_on_signup
+    assert_no_difference 'User.count' do
+      create_user(:email => nil)
+      assert assigns(:user).errors.on(:email)
+      assert_response :success
+    end
+  end
+  
+
+  protected
+    def create_user(options = {})
+      post :create, :user => { :login => 'quire', :email => 'quire@example.com',
+        :password => 'quire', :password_confirmation => 'quire' }.merge(options)
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unit/user_test.rb	Wed Mar 05 03:57:54 2008 +0900
@@ -0,0 +1,101 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class UserTest < Test::Unit::TestCase
+  # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead.
+  # Then, you can remove it from this and the functional test.
+  include AuthenticatedTestHelper
+  fixtures :users
+
+  def test_should_create_user
+    assert_difference 'User.count' do
+      user = create_user
+      assert !user.new_record?, "#{user.errors.full_messages.to_sentence}"
+    end
+  end
+
+  def test_should_require_login
+    assert_no_difference 'User.count' do
+      u = create_user(:login => nil)
+      assert u.errors.on(:login)
+    end
+  end
+
+  def test_should_require_password
+    assert_no_difference 'User.count' do
+      u = create_user(:password => nil)
+      assert u.errors.on(:password)
+    end
+  end
+
+  def test_should_require_password_confirmation
+    assert_no_difference 'User.count' do
+      u = create_user(:password_confirmation => nil)
+      assert u.errors.on(:password_confirmation)
+    end
+  end
+
+  def test_should_require_email
+    assert_no_difference 'User.count' do
+      u = create_user(:email => nil)
+      assert u.errors.on(:email)
+    end
+  end
+
+  def test_should_reset_password
+    users(:quentin).update_attributes(:password => 'new password', :password_confirmation => 'new password')
+    assert_equal users(:quentin), User.authenticate('quentin', 'new password')
+  end
+
+  def test_should_not_rehash_password
+    users(:quentin).update_attributes(:login => 'quentin2')
+    assert_equal users(:quentin), User.authenticate('quentin2', 'test')
+  end
+
+  def test_should_authenticate_user
+    assert_equal users(:quentin), User.authenticate('quentin', 'test')
+  end
+
+  def test_should_set_remember_token
+    users(:quentin).remember_me
+    assert_not_nil users(:quentin).remember_token
+    assert_not_nil users(:quentin).remember_token_expires_at
+  end
+
+  def test_should_unset_remember_token
+    users(:quentin).remember_me
+    assert_not_nil users(:quentin).remember_token
+    users(:quentin).forget_me
+    assert_nil users(:quentin).remember_token
+  end
+
+  def test_should_remember_me_for_one_week
+    before = 1.week.from_now.utc
+    users(:quentin).remember_me_for 1.week
+    after = 1.week.from_now.utc
+    assert_not_nil users(:quentin).remember_token
+    assert_not_nil users(:quentin).remember_token_expires_at
+    assert users(:quentin).remember_token_expires_at.between?(before, after)
+  end
+
+  def test_should_remember_me_until_one_week
+    time = 1.week.from_now.utc
+    users(:quentin).remember_me_until time
+    assert_not_nil users(:quentin).remember_token
+    assert_not_nil users(:quentin).remember_token_expires_at
+    assert_equal users(:quentin).remember_token_expires_at, time
+  end
+
+  def test_should_remember_me_default_two_weeks
+    before = 2.weeks.from_now.utc
+    users(:quentin).remember_me
+    after = 2.weeks.from_now.utc
+    assert_not_nil users(:quentin).remember_token
+    assert_not_nil users(:quentin).remember_token_expires_at
+    assert users(:quentin).remember_token_expires_at.between?(before, after)
+  end
+
+protected
+  def create_user(options = {})
+    User.create({ :login => 'quire', :email => 'quire@example.com', :password => 'quire', :password_confirmation => 'quire' }.merge(options))
+  end
+end