Commit 42f23f99d6a4a701b9ba82f623e084a1e15c8768

Authentication using restfull_authentication plugin.
Just the generated code
  
1# This controller handles the login/logout function of the site.
2class SessionsController < ApplicationController
3 # Be sure to include AuthenticationSystem in Application Controller instead
4 include AuthenticatedSystem
5
6 # render new.rhtml
7 def new
8 end
9
10 def create
11 self.current_user = User.authenticate(params[:login], params[:password])
12 if logged_in?
13 if params[:remember_me] == "1"
14 self.current_user.remember_me
15 cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
16 end
17 redirect_back_or_default('/')
18 flash[:notice] = "Logged in successfully"
19 else
20 render :action => 'new'
21 end
22 end
23
24 def destroy
25 self.current_user.forget_me if logged_in?
26 cookies.delete :auth_token
27 reset_session
28 flash[:notice] = "You have been logged out."
29 redirect_back_or_default('/')
30 end
31end
  
1class UsersController < ApplicationController
2 # Be sure to include AuthenticationSystem in Application Controller instead
3 include AuthenticatedSystem
4
5
6 # render new.rhtml
7 def new
8 end
9
10 def create
11 cookies.delete :auth_token
12 # protects against session fixation attacks, wreaks havoc with
13 # request forgery protection.
14 # uncomment at your own risk
15 # reset_session
16 @user = User.new(params[:user])
17 @user.save
18 if @user.errors.empty?
19 self.current_user = @user
20 redirect_back_or_default('/')
21 flash[:notice] = "Thanks for signing up!"
22 else
23 render :action => 'new'
24 end
25 end
26
27end
  
1module SessionsHelper
2end
  
1module UsersHelper
2end
  
1require 'digest/sha1'
2class User < ActiveRecord::Base
3 # Virtual attribute for the unencrypted password
4 attr_accessor :password
5
6 validates_presence_of :login, :email
7 validates_presence_of :password, :if => :password_required?
8 validates_presence_of :password_confirmation, :if => :password_required?
9 validates_length_of :password, :within => 4..40, :if => :password_required?
10 validates_confirmation_of :password, :if => :password_required?
11 validates_length_of :login, :within => 3..40
12 validates_length_of :email, :within => 3..100
13 validates_uniqueness_of :login, :email, :case_sensitive => false
14 before_save :encrypt_password
15
16 # prevents a user from submitting a crafted form that bypasses activation
17 # anything else you want your user to change should be added here.
18 attr_accessible :login, :email, :password, :password_confirmation
19
20 # Authenticates a user by their login name and unencrypted password. Returns the user or nil.
21 def self.authenticate(login, password)
22 u = find_by_login(login) # need to get the salt
23 u && u.authenticated?(password) ? u : nil
24 end
25
26 # Encrypts some data with the salt.
27 def self.encrypt(password, salt)
28 Digest::SHA1.hexdigest("--#{salt}--#{password}--")
29 end
30
31 # Encrypts the password with the user salt
32 def encrypt(password)
33 self.class.encrypt(password, salt)
34 end
35
36 def authenticated?(password)
37 crypted_password == encrypt(password)
38 end
39
40 def remember_token?
41 remember_token_expires_at && Time.now.utc < remember_token_expires_at
42 end
43
44 # These create and unset the fields required for remembering users between browser closes
45 def remember_me
46 remember_me_for 2.weeks
47 end
48
49 def remember_me_for(time)
50 remember_me_until time.from_now.utc
51 end
52
53 def remember_me_until(time)
54 self.remember_token_expires_at = time
55 self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
56 save(false)
57 end
58
59 def forget_me
60 self.remember_token_expires_at = nil
61 self.remember_token = nil
62 save(false)
63 end
64
65 protected
66 # before filter
67 def encrypt_password
68 return if password.blank?
69 self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
70 self.crypted_password = encrypt(password)
71 end
72
73 def password_required?
74 crypted_password.blank? || !password.blank?
75 end
76
77
78end
  
1<% form_tag session_path do -%>
2<p><label for="login">Login</label><br/>
3<%= text_field_tag 'login' %></p>
4
5<p><label for="password">Password</label><br/>
6<%= password_field_tag 'password' %></p>
7
8<!-- Uncomment this if you want this functionality
9<p><label for="remember_me">Remember me:</label>
10<%= check_box_tag 'remember_me' %></p>
11-->
12
13<p><%= submit_tag 'Log in' %></p>
14<% end -%>
  
1<%= error_messages_for :user %>
2<% form_for :user, :url => users_path do |f| -%>
3<p><label for="login">Login</label><br/>
4<%= f.text_field :login %></p>
5
6<p><label for="email">Email</label><br/>
7<%= f.text_field :email %></p>
8
9<p><label for="password">Password</label><br/>
10<%= f.password_field :password %></p>
11
12<p><label for="password_confirmation">Confirm Password</label><br/>
13<%= f.password_field :password_confirmation %></p>
14
15<p><%= submit_tag 'Sign up' %></p>
16<% end -%>
  
11ActionController::Routing::Routes.draw do |map|
2 map.resources :users
3
4 map.resource :session
5
26 map.resources :scheduled_debts
37 map.resources :realizations
48 map.resources :scheduled_profits
  
1class CreateUsers < ActiveRecord::Migration
2 def self.up
3 create_table "users", :force => true do |t|
4 t.column :login, :string
5 t.column :email, :string
6 t.column :crypted_password, :string, :limit => 40
7 t.column :salt, :string, :limit => 40
8 t.column :created_at, :datetime
9 t.column :updated_at, :datetime
10 t.column :remember_token, :string
11 t.column :remember_token_expires_at, :datetime
12
13
14 end
15 end
16
17 def self.down
18 drop_table "users"
19 end
20end
  
1module AuthenticatedSystem
2 protected
3 # Returns true or false if the user is logged in.
4 # Preloads @current_user with the user model if they're logged in.
5 def logged_in?
6 current_user != :false
7 end
8
9 # Accesses the current user from the session. Set it to :false if login fails
10 # so that future calls do not hit the database.
11 def current_user
12 @current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie || :false)
13 end
14
15 # Store the given user id in the session.
16 def current_user=(new_user)
17 session[:user_id] = (new_user.nil? || new_user.is_a?(Symbol)) ? nil : new_user.id
18 @current_user = new_user || :false
19 end
20
21 # Check if the user is authorized
22 #
23 # Override this method in your controllers if you want to restrict access
24 # to only a few actions or if you want to check if the user
25 # has the correct rights.
26 #
27 # Example:
28 #
29 # # only allow nonbobs
30 # def authorized?
31 # current_user.login != "bob"
32 # end
33 def authorized?
34 logged_in?
35 end
36
37 # Filter method to enforce a login requirement.
38 #
39 # To require logins for all actions, use this in your controllers:
40 #
41 # before_filter :login_required
42 #
43 # To require logins for specific actions, use this in your controllers:
44 #
45 # before_filter :login_required, :only => [ :edit, :update ]
46 #
47 # To skip this in a subclassed controller:
48 #
49 # skip_before_filter :login_required
50 #
51 def login_required
52 authorized? || access_denied
53 end
54
55 # Redirect as appropriate when an access request fails.
56 #
57 # The default action is to redirect to the login screen.
58 #
59 # Override this method in your controllers if you want to have special
60 # behavior in case the user is not authorized
61 # to access the requested action. For example, a popup window might
62 # simply close itself.
63 def access_denied
64 respond_to do |format|
65 format.html do
66 store_location
67 redirect_to new_session_path
68 end
69 format.any do
70 request_http_basic_authentication 'Web Password'
71 end
72 end
73 end
74
75 # Store the URI of the current request in the session.
76 #
77 # We can return to this location by calling #redirect_back_or_default.
78 def store_location
79 session[:return_to] = request.request_uri
80 end
81
82 # Redirect to the URI stored by the most recent store_location call or
83 # to the passed default.
84 def redirect_back_or_default(default)
85 redirect_to(session[:return_to] || default)
86 session[:return_to] = nil
87 end
88
89 # Inclusion hook to make #current_user and #logged_in?
90 # available as ActionView helper methods.
91 def self.included(base)
92 base.send :helper_method, :current_user, :logged_in?
93 end
94
95 # Called from #current_user. First attempt to login by the user id stored in the session.
96 def login_from_session
97 self.current_user = User.find(session[:user_id]) if session[:user_id]
98 end
99
100 # Called from #current_user. Now, attempt to login by basic authentication information.
101 def login_from_basic_auth
102 authenticate_with_http_basic do |username, password|
103 self.current_user = User.authenticate(username, password)
104 end
105 end
106
107 # Called from #current_user. Finaly, attempt to login by an expiring token in the cookie.
108 def login_from_cookie
109 user = cookies[:auth_token] && User.find_by_remember_token(cookies[:auth_token])
110 if user && user.remember_token?
111 user.remember_me
112 cookies[:auth_token] = { :value => user.remember_token, :expires => user.remember_token_expires_at }
113 self.current_user = user
114 end
115 end
116end
  
1module AuthenticatedTestHelper
2 # Sets the current user in the session from the user fixtures.
3 def login_as(user)
4 @request.session[:user_id] = user ? users(user).id : nil
5 end
6
7 def authorize_as(user)
8 @request.env["HTTP_AUTHORIZATION"] = user ? ActionController::HttpAuthentication::Basic.encode_credentials(users(user).login, 'test') : nil
9 end
10end
  
1body { background-color: #fff; color: #333; }
2
3body, p, ol, ul, td {
4 font-family: verdana, arial, helvetica, sans-serif;
5 font-size: 13px;
6 line-height: 18px;
7}
8
9pre {
10 background-color: #eee;
11 padding: 10px;
12 font-size: 11px;
13}
14
15a { color: #000; }
16a:visited { color: #666; }
17a:hover { color: #fff; background-color:#000; }
18
19.fieldWithErrors {
20 padding: 2px;
21 background-color: red;
22 display: table;
23}
24
25#errorExplanation {
26 width: 400px;
27 border: 2px solid red;
28 padding: 7px;
29 padding-bottom: 12px;
30 margin-bottom: 20px;
31 background-color: #f0f0f0;
32}
33
34#errorExplanation h2 {
35 text-align: left;
36 font-weight: bold;
37 padding: 5px 5px 5px 15px;
38 font-size: 12px;
39 margin: -7px;
40 background-color: #c00;
41 color: #fff;
42}
43
44#errorExplanation p {
45 color: #333;
46 margin-bottom: 0;
47 padding: 5px;
48}
49
50#errorExplanation ul li {
51 font-size: 12px;
52 list-style: square;
53}
54
55div.uploadStatus {
56 margin: 5px;
57}
58
59div.progressBar {
60 margin: 5px;
61}
62
63div.progressBar div.border {
64 background-color: #fff;
65 border: 1px solid gray;
66 width: 100%;
67}
68
69div.progressBar div.background {
70 background-color: #333;
71 height: 18px;
72 width: 0%;
73}
  
1quentin:
2 id: 1
3 login: quentin
4 email: quentin@example.com
5 salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
6 crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
7 created_at: <%= 5.days.ago.to_s :db %>
8
9
10
11aaron:
12 id: 2
13 login: aaron
14 email: aaron@example.com
15 salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
16 crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
17 created_at: <%= 1.days.ago.to_s :db %>
  
1require File.dirname(__FILE__) + '/../test_helper'
2require 'sessions_controller'
3
4# Re-raise errors caught by the controller.
5class SessionsController; def rescue_action(e) raise e end; end
6
7class SessionsControllerTest < Test::Unit::TestCase
8 # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead
9 # Then, you can remove it from this and the units test.
10 include AuthenticatedTestHelper
11
12 fixtures :users
13
14 def setup
15 @controller = SessionsController.new
16 @request = ActionController::TestRequest.new
17 @response = ActionController::TestResponse.new
18 end
19
20 def test_should_login_and_redirect
21 post :create, :login => 'quentin', :password => 'test'
22 assert session[:user_id]
23 assert_response :redirect
24 end
25
26 def test_should_fail_login_and_not_redirect
27 post :create, :login => 'quentin', :password => 'bad password'
28 assert_nil session[:user_id]
29 assert_response :success
30 end
31
32 def test_should_logout
33 login_as :quentin
34 get :destroy
35 assert_nil session[:user_id]
36 assert_response :redirect
37 end
38
39 def test_should_remember_me
40 post :create, :login => 'quentin', :password => 'test', :remember_me => "1"
41 assert_not_nil @response.cookies["auth_token"]
42 end
43
44 def test_should_not_remember_me
45 post :create, :login => 'quentin', :password => 'test', :remember_me => "0"
46 assert_nil @response.cookies["auth_token"]
47 end
48
49 def test_should_delete_token_on_logout
50 login_as :quentin
51 get :destroy
52 assert_equal @response.cookies["auth_token"], []
53 end
54
55 def test_should_login_with_cookie
56 users(:quentin).remember_me
57 @request.cookies["auth_token"] = cookie_for(:quentin)
58 get :new
59 assert @controller.send(:logged_in?)
60 end
61
62 def test_should_fail_expired_cookie_login
63 users(:quentin).remember_me
64 users(:quentin).update_attribute :remember_token_expires_at, 5.minutes.ago
65 @request.cookies["auth_token"] = cookie_for(:quentin)
66 get :new
67 assert !@controller.send(:logged_in?)
68 end
69
70 def test_should_fail_cookie_login
71 users(:quentin).remember_me
72 @request.cookies["auth_token"] = auth_token('invalid_auth_token')
73 get :new
74 assert !@controller.send(:logged_in?)
75 end
76
77 protected
78 def auth_token(token)
79 CGI::Cookie.new('name' => 'auth_token', 'value' => token)
80 end
81
82 def cookie_for(user)
83 auth_token users(user).remember_token
84 end
85end
  
1require File.dirname(__FILE__) + '/../test_helper'
2require 'users_controller'
3
4# Re-raise errors caught by the controller.
5class UsersController; def rescue_action(e) raise e end; end
6
7class UsersControllerTest < Test::Unit::TestCase
8 # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead
9 # Then, you can remove it from this and the units test.
10 include AuthenticatedTestHelper
11
12 fixtures :users
13
14 def setup
15 @controller = UsersController.new
16 @request = ActionController::TestRequest.new
17 @response = ActionController::TestResponse.new
18 end
19
20 def test_should_allow_signup
21 assert_difference 'User.count' do
22 create_user
23 assert_response :redirect
24 end
25 end
26
27 def test_should_require_login_on_signup
28 assert_no_difference 'User.count' do
29 create_user(:login => nil)
30 assert assigns(:user).errors.on(:login)
31 assert_response :success
32 end
33 end
34
35 def test_should_require_password_on_signup
36 assert_no_difference 'User.count' do
37 create_user(:password => nil)
38 assert assigns(:user).errors.on(:password)
39 assert_response :success
40 end
41 end
42
43 def test_should_require_password_confirmation_on_signup
44 assert_no_difference 'User.count' do
45 create_user(:password_confirmation => nil)
46 assert assigns(:user).errors.on(:password_confirmation)
47 assert_response :success
48 end
49 end
50
51 def test_should_require_email_on_signup
52 assert_no_difference 'User.count' do
53 create_user(:email => nil)
54 assert assigns(:user).errors.on(:email)
55 assert_response :success
56 end
57 end
58
59
60 protected
61 def create_user(options = {})
62 post :create, :user => { :login => 'quire', :email => 'quire@example.com',
63 :password => 'quire', :password_confirmation => 'quire' }.merge(options)
64 end
65end
  
1require File.dirname(__FILE__) + '/../test_helper'
2
3class UserTest < Test::Unit::TestCase
4 # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead.
5 # Then, you can remove it from this and the functional test.
6 include AuthenticatedTestHelper
7 fixtures :users
8
9 def test_should_create_user
10 assert_difference 'User.count' do
11 user = create_user
12 assert !user.new_record?, "#{user.errors.full_messages.to_sentence}"
13 end
14 end
15
16 def test_should_require_login
17 assert_no_difference 'User.count' do
18 u = create_user(:login => nil)
19 assert u.errors.on(:login)
20 end
21 end
22
23 def test_should_require_password
24 assert_no_difference 'User.count' do
25 u = create_user(:password => nil)
26 assert u.errors.on(:password)
27 end
28 end
29
30 def test_should_require_password_confirmation
31 assert_no_difference 'User.count' do
32 u = create_user(:password_confirmation => nil)
33 assert u.errors.on(:password_confirmation)
34 end
35 end
36
37 def test_should_require_email
38 assert_no_difference 'User.count' do
39 u = create_user(:email => nil)
40 assert u.errors.on(:email)
41 end
42 end
43
44 def test_should_reset_password
45 users(:quentin).update_attributes(:password => 'new password', :password_confirmation => 'new password')
46 assert_equal users(:quentin), User.authenticate('quentin', 'new password')
47 end
48
49 def test_should_not_rehash_password
50 users(:quentin).update_attributes(:login => 'quentin2')
51 assert_equal users(:quentin), User.authenticate('quentin2', 'test')
52 end
53
54 def test_should_authenticate_user
55 assert_equal users(:quentin), User.authenticate('quentin', 'test')
56 end
57
58 def test_should_set_remember_token
59 users(:quentin).remember_me
60 assert_not_nil users(:quentin).remember_token
61 assert_not_nil users(:quentin).remember_token_expires_at
62 end
63
64 def test_should_unset_remember_token
65 users(:quentin).remember_me
66 assert_not_nil users(:quentin).remember_token
67 users(:quentin).forget_me
68 assert_nil users(:quentin).remember_token
69 end
70
71 def test_should_remember_me_for_one_week
72 before = 1.week.from_now.utc
73 users(:quentin).remember_me_for 1.week
74 after = 1.week.from_now.utc
75 assert_not_nil users(:quentin).remember_token
76 assert_not_nil users(:quentin).remember_token_expires_at
77 assert users(:quentin).remember_token_expires_at.between?(before, after)
78 end
79
80 def test_should_remember_me_until_one_week
81 time = 1.week.from_now.utc
82 users(:quentin).remember_me_until time
83 assert_not_nil users(:quentin).remember_token
84 assert_not_nil users(:quentin).remember_token_expires_at
85 assert_equal users(:quentin).remember_token_expires_at, time
86 end
87
88 def test_should_remember_me_default_two_weeks
89 before = 2.weeks.from_now.utc
90 users(:quentin).remember_me
91 after = 2.weeks.from_now.utc
92 assert_not_nil users(:quentin).remember_token
93 assert_not_nil users(:quentin).remember_token_expires_at
94 assert users(:quentin).remember_token_expires_at.between?(before, after)
95 end
96
97protected
98 def create_user(options = {})
99 User.create({ :login => 'quire', :email => 'quire@example.com', :password => 'quire', :password_confirmation => 'quire' }.merge(options))
100 end
101end