Commit a3d9f0bbfdae40889c49c78c33044e2001012559
1 parent
4f51c61b
Exists in
master
and in
22 other branches
ActionItem9: adding user accounts model and controller, just generated acts_as_authenticated code
git-svn-id: https://svn.colivre.coop.br/svn/noosfero/trunk@94 3f533792-8f58-4932-b0fe-aaf55b0a4547
Showing
15 changed files
with
690 additions
and
0 deletions
Show diff stats
@@ -0,0 +1,43 @@ | @@ -0,0 +1,43 @@ | ||
1 | +class AccountController < ApplicationController | ||
2 | + # Be sure to include AuthenticationSystem in Application Controller instead | ||
3 | + include AuthenticatedSystem | ||
4 | + # If you want "remember me" functionality, add this before_filter to Application Controller | ||
5 | + before_filter :login_from_cookie | ||
6 | + | ||
7 | + # say something nice, you goof! something sweet. | ||
8 | + def index | ||
9 | + redirect_to(:action => 'signup') unless logged_in? || User.count > 0 | ||
10 | + end | ||
11 | + | ||
12 | + def login | ||
13 | + return unless request.post? | ||
14 | + self.current_user = User.authenticate(params[:login], params[:password]) | ||
15 | + if logged_in? | ||
16 | + if params[:remember_me] == "1" | ||
17 | + self.current_user.remember_me | ||
18 | + cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at } | ||
19 | + end | ||
20 | + redirect_back_or_default(:controller => '/account', :action => 'index') | ||
21 | + flash[:notice] = "Logged in successfully" | ||
22 | + end | ||
23 | + end | ||
24 | + | ||
25 | + def signup | ||
26 | + @user = User.new(params[:user]) | ||
27 | + return unless request.post? | ||
28 | + @user.save! | ||
29 | + self.current_user = @user | ||
30 | + redirect_back_or_default(:controller => '/account', :action => 'index') | ||
31 | + flash[:notice] = "Thanks for signing up!" | ||
32 | + rescue ActiveRecord::RecordInvalid | ||
33 | + render :action => 'signup' | ||
34 | + end | ||
35 | + | ||
36 | + def logout | ||
37 | + self.current_user.forget_me if logged_in? | ||
38 | + cookies.delete :auth_token | ||
39 | + reset_session | ||
40 | + flash[:notice] = "You have been logged out." | ||
41 | + redirect_back_or_default(:controller => '/account', :action => 'index') | ||
42 | + end | ||
43 | +end |
@@ -0,0 +1,64 @@ | @@ -0,0 +1,64 @@ | ||
1 | +require 'digest/sha1' | ||
2 | +class 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 | + # Authenticates a user by their login name and unencrypted password. Returns the user or nil. | ||
17 | + def self.authenticate(login, password) | ||
18 | + u = find_by_login(login) # need to get the salt | ||
19 | + u && u.authenticated?(password) ? u : nil | ||
20 | + end | ||
21 | + | ||
22 | + # Encrypts some data with the salt. | ||
23 | + def self.encrypt(password, salt) | ||
24 | + Digest::SHA1.hexdigest("--#{salt}--#{password}--") | ||
25 | + end | ||
26 | + | ||
27 | + # Encrypts the password with the user salt | ||
28 | + def encrypt(password) | ||
29 | + self.class.encrypt(password, salt) | ||
30 | + end | ||
31 | + | ||
32 | + def authenticated?(password) | ||
33 | + crypted_password == encrypt(password) | ||
34 | + end | ||
35 | + | ||
36 | + def remember_token? | ||
37 | + remember_token_expires_at && Time.now.utc < remember_token_expires_at | ||
38 | + end | ||
39 | + | ||
40 | + # These create and unset the fields required for remembering users between browser closes | ||
41 | + def remember_me | ||
42 | + self.remember_token_expires_at = 2.weeks.from_now.utc | ||
43 | + self.remember_token = encrypt("#{email}--#{remember_token_expires_at}") | ||
44 | + save(false) | ||
45 | + end | ||
46 | + | ||
47 | + def forget_me | ||
48 | + self.remember_token_expires_at = nil | ||
49 | + self.remember_token = nil | ||
50 | + save(false) | ||
51 | + end | ||
52 | + | ||
53 | + protected | ||
54 | + # before filter | ||
55 | + def encrypt_password | ||
56 | + return if password.blank? | ||
57 | + self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record? | ||
58 | + self.crypted_password = encrypt(password) | ||
59 | + end | ||
60 | + | ||
61 | + def password_required? | ||
62 | + crypted_password.blank? || !password.blank? | ||
63 | + end | ||
64 | +end |
@@ -0,0 +1,56 @@ | @@ -0,0 +1,56 @@ | ||
1 | +<h1>In the Caboose.</h1> | ||
2 | + | ||
3 | +<% content_for 'poem' do -%> | ||
4 | +"Train delayed? and what's to say?" | ||
5 | +"Blocked by last night's snow they say." | ||
6 | +Seven hours or so to wait; | ||
7 | +Well, that's pleasant! but there's the freight. | ||
8 | +Depot loafing no one fancies, | ||
9 | +We'll try the caboose and take our chances. | ||
10 | + | ||
11 | +Cool this morning in Watertown, | ||
12 | +Somewhat frosty___mercury down; | ||
13 | +Enter caboose___roaring fire, | ||
14 | +With never an air-hole; heat so dire | ||
15 | +That we shrivel and pant; we are roasted through- | ||
16 | +Outside, thermometer thirty-two. | ||
17 | + | ||
18 | +We start with a jerk and suddenly stop. | ||
19 | +"What's broke?" says one; another "What's up?", | ||
20 | +"Oh, nothing," they answer, "That's our way: | ||
21 | +You must stand the jerking, sorry to say." | ||
22 | +We "stand it" with oft this painful thought: | ||
23 | +Are our heads on yet, or are they not? | ||
24 | + | ||
25 | +Comrades in misery___let me see; | ||
26 | +Girl like a statue opposite me; | ||
27 | +Back and forth the others jostle___ | ||
28 | +She never winks, nor moves a muscle; | ||
29 | +See her, as she sits there now; | ||
30 | +She's "well balanced," anyhow. | ||
31 | + | ||
32 | +Woman in trouble, tearful eyes, | ||
33 | +Sits by the window, softly cries, | ||
34 | +Pity___for griefs we may not know, | ||
35 | +For breasts that ache, for tears that flow, | ||
36 | +Though we know not why. Her eyelids red | ||
37 | +Tell a sorrowful tale___some hope is dead. | ||
38 | + | ||
39 | +Man who follows the Golden Rule, | ||
40 | +And lends his papers___a pocket full, | ||
41 | +Has a blank book___once in a minute | ||
42 | +Has an idea, and writes it in it. | ||
43 | +Guess him? Yes, of course I can, | ||
44 | +He's a___well___a newspaper man. | ||
45 | + | ||
46 | +Blue-eyed fairy, wrapped in fur; | ||
47 | +Sweet young mother tending her. | ||
48 | +Fairy thinks it's "awful far," | ||
49 | +Wants to get off this "naughty car." | ||
50 | +So do we, young golden-hair; | ||
51 | +All this crowd are with you there! | ||
52 | +<% end -%> | ||
53 | + | ||
54 | +<%= simple_format @content_for_poem %> | ||
55 | + | ||
56 | +<p><a href="http://skyways.lib.ks.us/poetry/walls/caboose.html">-- Ellen P. Allerton.</a></p> | ||
0 | \ No newline at end of file | 57 | \ No newline at end of file |
@@ -0,0 +1,14 @@ | @@ -0,0 +1,14 @@ | ||
1 | +<% form_tag 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 -%> |
@@ -0,0 +1,16 @@ | @@ -0,0 +1,16 @@ | ||
1 | +<%= error_messages_for :user %> | ||
2 | +<% form_for :user 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 -%> |
@@ -0,0 +1,16 @@ | @@ -0,0 +1,16 @@ | ||
1 | +<html> | ||
2 | + <head> | ||
3 | + <%= javascript_include_tag :defaults %> | ||
4 | + <%= javascript_include_tag_template @chosen_template %> | ||
5 | + <%= stylesheet_link_tag_template @chosen_template %> | ||
6 | + | ||
7 | + </head> | ||
8 | + <body> | ||
9 | + | ||
10 | + <div id='main'> | ||
11 | + <%= yield %> | ||
12 | + </div> | ||
13 | + | ||
14 | + </body> | ||
15 | + | ||
16 | +</html> |
config/routes.rb
@@ -13,6 +13,9 @@ ActionController::Routing::Routes.draw do |map| | @@ -13,6 +13,9 @@ ActionController::Routing::Routes.draw do |map| | ||
13 | # -- just remember to delete public/index.html. | 13 | # -- just remember to delete public/index.html. |
14 | map.connect '', :controller => "home" | 14 | map.connect '', :controller => "home" |
15 | 15 | ||
16 | + # user account controller | ||
17 | + map.connect 'account/:action', :controller => 'account' | ||
18 | + | ||
16 | # administrative tasks for a virtual community | 19 | # administrative tasks for a virtual community |
17 | map.connect 'admin/:controller/:action/:id' | 20 | map.connect 'admin/:controller/:action/:id' |
18 | 21 |
@@ -0,0 +1,18 @@ | @@ -0,0 +1,18 @@ | ||
1 | +class 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 | + end | ||
13 | + end | ||
14 | + | ||
15 | + def self.down | ||
16 | + drop_table "users" | ||
17 | + end | ||
18 | +end |
@@ -0,0 +1,120 @@ | @@ -0,0 +1,120 @@ | ||
1 | +module 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. | ||
10 | + def current_user | ||
11 | + @current_user ||= (session[:user] && User.find_by_id(session[:user])) || :false | ||
12 | + end | ||
13 | + | ||
14 | + # Store the given user in the session. | ||
15 | + def current_user=(new_user) | ||
16 | + session[:user] = (new_user.nil? || new_user.is_a?(Symbol)) ? nil : new_user.id | ||
17 | + @current_user = new_user | ||
18 | + end | ||
19 | + | ||
20 | + # Check if the user is authorized. | ||
21 | + # | ||
22 | + # Override this method in your controllers if you want to restrict access | ||
23 | + # to only a few actions or if you want to check if the user | ||
24 | + # has the correct rights. | ||
25 | + # | ||
26 | + # Example: | ||
27 | + # | ||
28 | + # # only allow nonbobs | ||
29 | + # def authorize? | ||
30 | + # current_user.login != "bob" | ||
31 | + # end | ||
32 | + def authorized? | ||
33 | + true | ||
34 | + end | ||
35 | + | ||
36 | + # Filter method to enforce a login requirement. | ||
37 | + # | ||
38 | + # To require logins for all actions, use this in your controllers: | ||
39 | + # | ||
40 | + # before_filter :login_required | ||
41 | + # | ||
42 | + # To require logins for specific actions, use this in your controllers: | ||
43 | + # | ||
44 | + # before_filter :login_required, :only => [ :edit, :update ] | ||
45 | + # | ||
46 | + # To skip this in a subclassed controller: | ||
47 | + # | ||
48 | + # skip_before_filter :login_required | ||
49 | + # | ||
50 | + def login_required | ||
51 | + username, passwd = get_auth_data | ||
52 | + self.current_user ||= User.authenticate(username, passwd) || :false if username && passwd | ||
53 | + logged_in? && authorized? ? true : access_denied | ||
54 | + end | ||
55 | + | ||
56 | + # Redirect as appropriate when an access request fails. | ||
57 | + # | ||
58 | + # The default action is to redirect to the login screen. | ||
59 | + # | ||
60 | + # Override this method in your controllers if you want to have special | ||
61 | + # behavior in case the user is not authorized | ||
62 | + # to access the requested action. For example, a popup window might | ||
63 | + # simply close itself. | ||
64 | + def access_denied | ||
65 | + respond_to do |accepts| | ||
66 | + accepts.html do | ||
67 | + store_location | ||
68 | + redirect_to :controller => '/account', :action => 'login' | ||
69 | + end | ||
70 | + accepts.xml do | ||
71 | + headers["Status"] = "Unauthorized" | ||
72 | + headers["WWW-Authenticate"] = %(Basic realm="Web Password") | ||
73 | + render :text => "Could't authenticate you", :status => '401 Unauthorized' | ||
74 | + end | ||
75 | + end | ||
76 | + false | ||
77 | + end | ||
78 | + | ||
79 | + # Store the URI of the current request in the session. | ||
80 | + # | ||
81 | + # We can return to this location by calling #redirect_back_or_default. | ||
82 | + def store_location | ||
83 | + session[:return_to] = request.request_uri | ||
84 | + end | ||
85 | + | ||
86 | + # Redirect to the URI stored by the most recent store_location call or | ||
87 | + # to the passed default. | ||
88 | + def redirect_back_or_default(default) | ||
89 | + session[:return_to] ? redirect_to_url(session[:return_to]) : redirect_to(default) | ||
90 | + session[:return_to] = nil | ||
91 | + end | ||
92 | + | ||
93 | + # Inclusion hook to make #current_user and #logged_in? | ||
94 | + # available as ActionView helper methods. | ||
95 | + def self.included(base) | ||
96 | + base.send :helper_method, :current_user, :logged_in? | ||
97 | + end | ||
98 | + | ||
99 | + # When called with before_filter :login_from_cookie will check for an :auth_token | ||
100 | + # cookie and log the user back in if apropriate | ||
101 | + def login_from_cookie | ||
102 | + return unless cookies[:auth_token] && !logged_in? | ||
103 | + user = User.find_by_remember_token(cookies[:auth_token]) | ||
104 | + if user && user.remember_token? | ||
105 | + user.remember_me | ||
106 | + self.current_user = user | ||
107 | + cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at } | ||
108 | + flash[:notice] = "Logged in successfully" | ||
109 | + end | ||
110 | + end | ||
111 | + | ||
112 | + private | ||
113 | + @@http_auth_headers = %w(X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION Authorization) | ||
114 | + # gets BASIC auth info | ||
115 | + def get_auth_data | ||
116 | + auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) } | ||
117 | + auth_data = request.env[auth_key].to_s.split unless auth_key.blank? | ||
118 | + return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil] | ||
119 | + end | ||
120 | +end |
@@ -0,0 +1,113 @@ | @@ -0,0 +1,113 @@ | ||
1 | +module AuthenticatedTestHelper | ||
2 | + # Sets the current user in the session from the user fixtures. | ||
3 | + def login_as(user) | ||
4 | + @request.session[:user] = user ? users(user).id : nil | ||
5 | + end | ||
6 | + | ||
7 | + def content_type(type) | ||
8 | + @request.env['Content-Type'] = type | ||
9 | + end | ||
10 | + | ||
11 | + def accept(accept) | ||
12 | + @request.env["HTTP_ACCEPT"] = accept | ||
13 | + end | ||
14 | + | ||
15 | + def authorize_as(user) | ||
16 | + if user | ||
17 | + @request.env["HTTP_AUTHORIZATION"] = "Basic #{Base64.encode64("#{users(user).login}:test")}" | ||
18 | + accept 'application/xml' | ||
19 | + content_type 'application/xml' | ||
20 | + else | ||
21 | + @request.env["HTTP_AUTHORIZATION"] = nil | ||
22 | + accept nil | ||
23 | + content_type nil | ||
24 | + end | ||
25 | + end | ||
26 | + | ||
27 | + # http://project.ioni.st/post/217#post-217 | ||
28 | + # | ||
29 | + # def test_new_publication | ||
30 | + # assert_difference(Publication, :count) do | ||
31 | + # post :create, :publication => {...} | ||
32 | + # # ... | ||
33 | + # end | ||
34 | + # end | ||
35 | + # | ||
36 | + def assert_difference(object, method = nil, difference = 1) | ||
37 | + initial_value = object.send(method) | ||
38 | + yield | ||
39 | + assert_equal initial_value + difference, object.send(method), "#{object}##{method}" | ||
40 | + end | ||
41 | + | ||
42 | + def assert_no_difference(object, method, &block) | ||
43 | + assert_difference object, method, 0, &block | ||
44 | + end | ||
45 | + | ||
46 | + # Assert the block redirects to the login | ||
47 | + # | ||
48 | + # assert_requires_login(:bob) { |c| c.get :edit, :id => 1 } | ||
49 | + # | ||
50 | + def assert_requires_login(login = nil) | ||
51 | + yield HttpLoginProxy.new(self, login) | ||
52 | + end | ||
53 | + | ||
54 | + def assert_http_authentication_required(login = nil) | ||
55 | + yield XmlLoginProxy.new(self, login) | ||
56 | + end | ||
57 | + | ||
58 | + def reset!(*instance_vars) | ||
59 | + instance_vars = [:controller, :request, :response] unless instance_vars.any? | ||
60 | + instance_vars.collect! { |v| "@#{v}".to_sym } | ||
61 | + instance_vars.each do |var| | ||
62 | + instance_variable_set(var, instance_variable_get(var).class.new) | ||
63 | + end | ||
64 | + end | ||
65 | +end | ||
66 | + | ||
67 | +class BaseLoginProxy | ||
68 | + attr_reader :controller | ||
69 | + attr_reader :options | ||
70 | + def initialize(controller, login) | ||
71 | + @controller = controller | ||
72 | + @login = login | ||
73 | + end | ||
74 | + | ||
75 | + private | ||
76 | + def authenticated | ||
77 | + raise NotImplementedError | ||
78 | + end | ||
79 | + | ||
80 | + def check | ||
81 | + raise NotImplementedError | ||
82 | + end | ||
83 | + | ||
84 | + def method_missing(method, *args) | ||
85 | + @controller.reset! | ||
86 | + authenticate | ||
87 | + @controller.send(method, *args) | ||
88 | + check | ||
89 | + end | ||
90 | +end | ||
91 | + | ||
92 | +class HttpLoginProxy < BaseLoginProxy | ||
93 | + protected | ||
94 | + def authenticate | ||
95 | + @controller.login_as @login if @login | ||
96 | + end | ||
97 | + | ||
98 | + def check | ||
99 | + @controller.assert_redirected_to :controller => 'account', :action => 'login' | ||
100 | + end | ||
101 | +end | ||
102 | + | ||
103 | +class XmlLoginProxy < BaseLoginProxy | ||
104 | + protected | ||
105 | + def authenticate | ||
106 | + @controller.accept 'application/xml' | ||
107 | + @controller.authorize_as @login if @login | ||
108 | + end | ||
109 | + | ||
110 | + def check | ||
111 | + @controller.assert_response 401 | ||
112 | + end | ||
113 | +end | ||
0 | \ No newline at end of file | 114 | \ No newline at end of file |
@@ -0,0 +1,17 @@ | @@ -0,0 +1,17 @@ | ||
1 | +quentin: | ||
2 | + id: 1 | ||
3 | + login: quentin | ||
4 | + email: quentin@example.com | ||
5 | + salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd | ||
6 | + crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test | ||
7 | + #crypted_password: "ce2/iFrNtQ8=\n" # quentin, use only if you're using 2-way encryption | ||
8 | + created_at: <%= 5.days.ago.to_s :db %> | ||
9 | + # activated_at: <%= 5.days.ago.to_s :db %> # only if you're activating new signups | ||
10 | +aaron: | ||
11 | + id: 2 | ||
12 | + login: aaron | ||
13 | + email: aaron@example.com | ||
14 | + salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd | ||
15 | + crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test | ||
16 | + # activation_code: aaronscode # only if you're activating new signups | ||
17 | + created_at: <%= 1.days.ago.to_s :db %> | ||
0 | \ No newline at end of file | 18 | \ No newline at end of file |
@@ -0,0 +1,129 @@ | @@ -0,0 +1,129 @@ | ||
1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
2 | +require 'account_controller' | ||
3 | + | ||
4 | +# Re-raise errors caught by the controller. | ||
5 | +class AccountController; def rescue_action(e) raise e end; end | ||
6 | + | ||
7 | +class AccountControllerTest < 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 = AccountController.new | ||
16 | + @request = ActionController::TestRequest.new | ||
17 | + @response = ActionController::TestResponse.new | ||
18 | + end | ||
19 | + | ||
20 | + def test_should_login_and_redirect | ||
21 | + post :login, :login => 'quentin', :password => 'test' | ||
22 | + assert session[:user] | ||
23 | + assert_response :redirect | ||
24 | + end | ||
25 | + | ||
26 | + def test_should_fail_login_and_not_redirect | ||
27 | + post :login, :login => 'quentin', :password => 'bad password' | ||
28 | + assert_nil session[:user] | ||
29 | + assert_response :success | ||
30 | + end | ||
31 | + | ||
32 | + def test_should_allow_signup | ||
33 | + assert_difference User, :count do | ||
34 | + create_user | ||
35 | + assert_response :redirect | ||
36 | + end | ||
37 | + end | ||
38 | + | ||
39 | + def test_should_require_login_on_signup | ||
40 | + assert_no_difference User, :count do | ||
41 | + create_user(:login => nil) | ||
42 | + assert assigns(:user).errors.on(:login) | ||
43 | + assert_response :success | ||
44 | + end | ||
45 | + end | ||
46 | + | ||
47 | + def test_should_require_password_on_signup | ||
48 | + assert_no_difference User, :count do | ||
49 | + create_user(:password => nil) | ||
50 | + assert assigns(:user).errors.on(:password) | ||
51 | + assert_response :success | ||
52 | + end | ||
53 | + end | ||
54 | + | ||
55 | + def test_should_require_password_confirmation_on_signup | ||
56 | + assert_no_difference User, :count do | ||
57 | + create_user(:password_confirmation => nil) | ||
58 | + assert assigns(:user).errors.on(:password_confirmation) | ||
59 | + assert_response :success | ||
60 | + end | ||
61 | + end | ||
62 | + | ||
63 | + def test_should_require_email_on_signup | ||
64 | + assert_no_difference User, :count do | ||
65 | + create_user(:email => nil) | ||
66 | + assert assigns(:user).errors.on(:email) | ||
67 | + assert_response :success | ||
68 | + end | ||
69 | + end | ||
70 | + | ||
71 | + def test_should_logout | ||
72 | + login_as :quentin | ||
73 | + get :logout | ||
74 | + assert_nil session[:user] | ||
75 | + assert_response :redirect | ||
76 | + end | ||
77 | + | ||
78 | + def test_should_remember_me | ||
79 | + post :login, :login => 'quentin', :password => 'test', :remember_me => "1" | ||
80 | + assert_not_nil @response.cookies["auth_token"] | ||
81 | + end | ||
82 | + | ||
83 | + def test_should_not_remember_me | ||
84 | + post :login, :login => 'quentin', :password => 'test', :remember_me => "0" | ||
85 | + assert_nil @response.cookies["auth_token"] | ||
86 | + end | ||
87 | + | ||
88 | + def test_should_delete_token_on_logout | ||
89 | + login_as :quentin | ||
90 | + get :logout | ||
91 | + assert_equal @response.cookies["auth_token"], [] | ||
92 | + end | ||
93 | + | ||
94 | + def test_should_login_with_cookie | ||
95 | + users(:quentin).remember_me | ||
96 | + @request.cookies["auth_token"] = cookie_for(:quentin) | ||
97 | + get :index | ||
98 | + assert @controller.send(:logged_in?) | ||
99 | + end | ||
100 | + | ||
101 | + def test_should_fail_expired_cookie_login | ||
102 | + users(:quentin).remember_me | ||
103 | + users(:quentin).update_attribute :remember_token_expires_at, 5.minutes.ago | ||
104 | + @request.cookies["auth_token"] = cookie_for(:quentin) | ||
105 | + get :index | ||
106 | + assert !@controller.send(:logged_in?) | ||
107 | + end | ||
108 | + | ||
109 | + def test_should_fail_cookie_login | ||
110 | + users(:quentin).remember_me | ||
111 | + @request.cookies["auth_token"] = auth_token('invalid_auth_token') | ||
112 | + get :index | ||
113 | + assert !@controller.send(:logged_in?) | ||
114 | + end | ||
115 | + | ||
116 | + protected | ||
117 | + def create_user(options = {}) | ||
118 | + post :signup, :user => { :login => 'quire', :email => 'quire@example.com', | ||
119 | + :password => 'quire', :password_confirmation => 'quire' }.merge(options) | ||
120 | + end | ||
121 | + | ||
122 | + def auth_token(token) | ||
123 | + CGI::Cookie.new('name' => 'auth_token', 'value' => token) | ||
124 | + end | ||
125 | + | ||
126 | + def cookie_for(user) | ||
127 | + auth_token users(user).remember_token | ||
128 | + end | ||
129 | +end |
test/integration/routing_test.rb
@@ -6,4 +6,8 @@ class RoutingTest < ActionController::IntegrationTest | @@ -6,4 +6,8 @@ class RoutingTest < ActionController::IntegrationTest | ||
6 | assert_routing('admin/features', :controller => 'features', :action => 'index') | 6 | assert_routing('admin/features', :controller => 'features', :action => 'index') |
7 | end | 7 | end |
8 | 8 | ||
9 | + def test_account_controller | ||
10 | + assert_routing('account', :controller => 'account', :action => 'index') | ||
11 | + end | ||
12 | + | ||
9 | end | 13 | end |
@@ -0,0 +1,75 @@ | @@ -0,0 +1,75 @@ | ||
1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
2 | + | ||
3 | +class 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 | + protected | ||
72 | + def create_user(options = {}) | ||
73 | + User.create({ :login => 'quire', :email => 'quire@example.com', :password => 'quire', :password_confirmation => 'quire' }.merge(options)) | ||
74 | + end | ||
75 | +end |