It also includes a new controller which handles verifying the user after they follow the link in the email.
You will need to run a migration with this patch.
To set the hostname for where the user goes to verify themselves, update the config/environments/{development, test, production}.rb file and enter the correct public hostname.
It introduces a new model object, UserVerification. When a user registers an account, an instance is created. As long as that object exists, the user cannot do anything with the system.
Signed-off-by: Darryl L. Pierce mcpierce@gmail.com --- app/controllers/home_controller.rb | 25 ---- app/controllers/user_controller.rb | 34 +++++ app/models/user.rb | 16 +++ app/models/user_mailer.rb | 17 ++- app/models/user_verification.rb | 45 +++++++ app/views/user_mailer/email_verification.html.erb | 14 ++ config/environments/development.rb | 27 ++-- config/environments/production.rb | 25 ++-- config/environments/test.rb | 25 ++-- config/routes.rb | 4 + db/migrate/016_create_user_verifications.rb | 20 +++ db/schema.rb | 20 +++- test/fixtures/user_verifications.yml | 6 + test/fixtures/users.yml | 8 +- test/functional/home_controller_test.rb | 33 +---- test/functional/user_controller_test.rb | 147 +++++++++++++++------ test/unit/user_test.rb | 26 ++++- test/unit/user_verification_test.rb | 77 +++++++++++ 18 files changed, 431 insertions(+), 138 deletions(-) create mode 100644 app/models/user_verification.rb create mode 100644 app/views/user/verify.html.erb create mode 100644 app/views/user_mailer/email_verification.html.erb create mode 100644 db/migrate/016_create_user_verifications.rb create mode 100644 test/fixtures/user_verifications.yml create mode 100644 test/unit/user_verification_test.rb
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 35dd883..6b8030c 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -72,31 +72,6 @@ class HomeController < ApplicationController @backlog = BacklogItem.find_all_by_state_and_owner_id(BacklogItem::STATE_ASSIGNED, session[:user_id]) end
- # Allows the user to register a new account. Users may only register if they - # are not currently logged into an account. TODO We need to authenticate the - # email address entered by a user. TODO Accounts that aren't verified afew a - # set time should be deleted. - # - def register - if @user - flash[:message] = 'You are already registered.' - redirect_to :action => :dashboard - else if request.post? - user = User.new(:email => params[:email], - :display_name => params[:display_name], - :introduction => params[:introduction]) - - user.privileges = UserPrivilege.new - - user.password = params[:password] - user.save! - - flash[:message] = 'Thank you. Please log in with your new account.' - redirect_to :action => :login - end - end - end - # Allows the user to email him a new password. # def generate_new_password diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index 6d13641..ebd6de6 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -26,6 +26,35 @@ class UserController < ApplicationController
end
+ def verify + if !@this_user.verified? + if params[:token] + if @this_user.verify(params[:token]) + begin + User.transaction do + @this_user.verification.destroy + @this_user.verification = nil + @this_user.save! + end + + flash[:message] = "#{@this_user.email} is now verified. Please login." + redirect_to :controller => :home, :action => :login + rescue Exception => error + flash[:error] = "ERROR: #{error.message}" + redirect_to error_url + end + else + flash[:error] = 'Verification failed.' + end + else + flash[:error] = 'No token was provided.' + end + else + flash[:error] = "#{@this_user.display_name} is already verified." + redirect_to error_url + end + end + def backlog
end @@ -36,8 +65,13 @@ class UserController < ApplicationController User.transaction do @this_user = User.new(params[:user]) @this_user.privileges = UserPrivilege.new + @this_user.verification = UserVerification.new( + :sent => Date.today, + :token => UserVerification.create_token)
@this_user.save! + + UserMailer.deliver_email_verification(@this_user,@this_user.verification.token) end
redirect_to :controller => :home, :action => :login diff --git a/app/models/user.rb b/app/models/user.rb index 6554bf4..7d26194 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -25,6 +25,7 @@ class User < ActiveRecord::Base
has_one :privileges, :class_name => "UserPrivilege", :dependent => :destroy has_many :product_roles + has_one :verification, :class_name => "UserVerification", :dependent => :destroy
attr_accessor :password_confirmation validates_confirmation_of :password @@ -77,6 +78,21 @@ class User < ActiveRecord::Base result end
+ # Attempts to verify the user by checking the supplied token against the + # existing verification token. + # + def verify(token) + raise Exception.new("User has no pending verifications!") if verification == nil + + verification.token == token + end + + # Returns whether the user has been verified. + # + def verified? + (verification == nil) + end + # Returns whether the user is allowed to create projects. # def create_projects? diff --git a/app/models/user_mailer.rb b/app/models/user_mailer.rb index bd42800..896a688 100644 --- a/app/models/user_mailer.rb +++ b/app/models/user_mailer.rb @@ -1,4 +1,13 @@ class UserMailer < ActionMailer::Base + # Sends an email verification to the specified user. + # + def email_verification(user, token) + recipients user.email + from MAIL_CONFIG[:from] + subject "Email verification" + body :user => user, :token => token + end + # Send an e-mail to an user and notify him his new password. # def new_generated_password(user, new_password) @@ -8,8 +17,8 @@ class UserMailer < ActionMailer::Base body :user => user, :new_password => new_password end
- # Send an email to a user to notify him that no activity has been detected - # in his backlog. + # Send an email to a user to notify him that no activity has been detected in + # his backlog. # def no_activity_recorded(user, backlog_items) recipients user.email @@ -18,8 +27,8 @@ class UserMailer < ActionMailer::Base body :user => user, :backlog_items => backlog_items end
- # Send an email to a user to notify him that no task has been detected - # in his backlog. + # Send an email to a user to notify him that no task has been detected in his + # backlog. # def no_task_recorded(user, backlog_items) recipients user.email diff --git a/app/models/user_verification.rb b/app/models/user_verification.rb new file mode 100644 index 0000000..0431fe0 --- /dev/null +++ b/app/models/user_verification.rb @@ -0,0 +1,45 @@ +# user_verification.rb +# Copyright (C) 2008, Darryl L. Pierce mcpierce@gmail.com +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see http://www.gnu.org/licenses/. +# + +# A +UserVerification+ object only exists when a user account needs to be +# verified. +# +# A verification contains a +token+ which must be received from the user in +# order to complete the validation. The token is 16 bytes long and consists only +# of alphanumeric characters. +# +class UserVerification < ActiveRecord::Base + TOKEN_CHARACTERS = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a + TOKEN_SIZE = 16 + TOKEN_PATTERN = "^[[:alnum:]]{#{TOKEN_SIZE}}$" + + validates_presence_of :user_id + + validates_presence_of :token + validates_format_of :token, + :with => /#{TOKEN_PATTERN}/ + + validates_presence_of :sent + + belongs_to :user + + # Generates a verification token. + # + def self.create_token + (0...TOKEN_SIZE).collect { TOKEN_CHARACTERS[Kernel.rand(TOKEN_CHARACTERS.length)] }.join + end +end diff --git a/app/views/user/verify.html.erb b/app/views/user/verify.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/app/views/user_mailer/email_verification.html.erb b/app/views/user_mailer/email_verification.html.erb new file mode 100644 index 0000000..3190ac4 --- /dev/null +++ b/app/views/user_mailer/email_verification.html.erb @@ -0,0 +1,14 @@ +Dear <%= @user.display_name %>, + +This email is sent to you to verify that the address you entered: + +<%= @user.email %> + +is valid. To confirm this email address, please go to: + +<%= email_verification_url :controller => "user", :action => "verify", :id => @user.id, :token => @token %> + +If this link fails, please go to the verification page and enter the token: + +<%= @token %> + diff --git a/config/environments/development.rb b/config/environments/development.rb index 9708140..420825d 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,18 +1,18 @@ -# developerment.rb -# Copyright (C) 2008, Darryl L. Pierce mcpierce@gmail.com +# developerment.rb +# Copyright (C) 2008, Darryl L. Pierce mcpierce@gmail.com # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. #
# Settings specified here will take precedence over those in config/environment.rb @@ -32,4 +32,5 @@ config.action_controller.perform_caching = false config.action_view.cache_template_extensions = false
# Don't care if the mailer can't send -config.action_mailer.raise_delivery_errors = false +config.action_mailer.raise_delivery_errors = true +config.action_mailer.default_url_options = { :host => 'localhost:3000' } diff --git a/config/environments/production.rb b/config/environments/production.rb index eb7195a..ada1fd1 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,18 +1,18 @@ -# production.rb -# Copyright (C) 2008, Darryl L. Pierce mcpierce@gmail.com +# production.rb +# Copyright (C) 2008, Darryl L. Pierce mcpierce@gmail.com # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. #
# Settings specified here will take precedence over those in config/environment.rb @@ -34,3 +34,4 @@ config.action_view.cache_template_loading = true
# Disable delivery errors, bad email addresses will be ignored # config.action_mailer.raise_delivery_errors = false +config.action_mailer.default_url_options = { :host => 'www.example.com' } diff --git a/config/environments/test.rb b/config/environments/test.rb index 00f6c34..e844d9b 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,18 +1,18 @@ -# test.rb -# Copyright (C) 2008, Darryl L. Pierce mcpierce@gmail.com +# test.rb +# Copyright (C) 2008, Darryl L. Pierce mcpierce@gmail.com # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. #
# Settings specified here will take precedence over those in config/environment.rb @@ -37,3 +37,4 @@ config.action_controller.allow_forgery_protection = false # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test +config.action_mailer.default_url_options = { :host => 'localhost' } diff --git a/config/routes.rb b/config/routes.rb index 1108059..0f9ef23 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -23,6 +23,10 @@ ActionController::Routing::Routes.draw do |map| # Error handling map.error "/error", :controller => 'home', :action => 'error'
+ map.email_verification "verify/:id/:token", + :controller => 'user', + :action => 'verify' + # Install the default routes as the lowest priority. map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' diff --git a/db/migrate/016_create_user_verifications.rb b/db/migrate/016_create_user_verifications.rb new file mode 100644 index 0000000..7f947dc --- /dev/null +++ b/db/migrate/016_create_user_verifications.rb @@ -0,0 +1,20 @@ +class CreateUserVerifications < ActiveRecord::Migration + def self.up + create_table :user_verifications do |t| + t.integer :user_id, :null => false + t.string :token, :null => false, :limit => 16 + t.datetime :sent, :null => false + + t.timestamps + end + + add_index :user_verifications, :user_id, :unique => true + + execute "alter table user_verifications add constraint fk_user_verification_user + foreign key (user_id) references users(id)" + end + + def self.down + drop_table :user_verifications + end +end diff --git a/db/schema.rb b/db/schema.rb index 4a20449..331a1e5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -23,6 +23,7 @@ ActiveRecord::Schema.define(:version => 15) do end
add_index "backlog_items", ["sprint_id", "user_story_id"], :name => "index_backlog_items_on_sprint_id_and_user_story_id", :unique => true + add_index "backlog_items", ["user_story_id"], :name => "fk_backlog_items_user_story"
create_table "product_roles", :force => true do |t| t.integer "user_id", :null => false @@ -32,7 +33,9 @@ ActiveRecord::Schema.define(:version => 15) do t.datetime "updated_at" end
- add_index "product_roles", ["product_id", "user_id"], :name => "index_product_roles_on_user_id_and_product_id", :unique => true + add_index "product_roles", ["user_id", "product_id"], :name => "index_product_roles_on_user_id_and_product_id", :unique => true + add_index "product_roles", ["product_id"], :name => "fk_user_role_product" + add_index "product_roles", ["role_id"], :name => "fk_user_role_role"
create_table "products", :force => true do |t| t.integer "project_id" @@ -44,6 +47,7 @@ ActiveRecord::Schema.define(:version => 15) do end
add_index "products", ["name"], :name => "index_products_on_name", :unique => true + add_index "products", ["project_id"], :name => "fk_products_project"
create_table "projects", :force => true do |t| t.integer "owner_id", :null => false @@ -55,6 +59,7 @@ ActiveRecord::Schema.define(:version => 15) do end
add_index "projects", ["name"], :name => "index_projects_on_name", :unique => true + add_index "projects", ["owner_id"], :name => "fk_product_owner"
create_table "remaining_hours_estimates", :force => true do |t| t.integer "backlog_item_id", :null => false @@ -65,6 +70,9 @@ ActiveRecord::Schema.define(:version => 15) do t.datetime "estimated_on", :null => false end
+ add_index "remaining_hours_estimates", ["backlog_item_id"], :name => "fk_remaining_hours_item" + add_index "remaining_hours_estimates", ["user_id"], :name => "fk_remaining_hours_user" + create_table "roles", :force => true do |t| t.string "name", :null => false t.boolean "can_manage_backlog_items", :default => false, :null => false @@ -96,6 +104,8 @@ ActiveRecord::Schema.define(:version => 15) do t.integer "status", :default => 0, :null => false end
+ add_index "sprints", ["product_id"], :name => "fk_sprint_product" + create_table "tasks", :force => true do |t| t.integer "backlog_item_id", :null => false t.integer "primary_id", :null => false @@ -107,6 +117,10 @@ ActiveRecord::Schema.define(:version => 15) do t.datetime "updated_at" end
+ add_index "tasks", ["backlog_item_id"], :name => "fk_task_backlog_item" + add_index "tasks", ["primary_id"], :name => "fk_task_primary" + add_index "tasks", ["backup_id"], :name => "fk_task_backup" + create_table "user_privileges", :force => true do |t| t.integer "user_id", :null => false t.boolean "admin_projects", :default => false, :null => false @@ -115,6 +129,8 @@ ActiveRecord::Schema.define(:version => 15) do t.datetime "updated_at" end
+ add_index "user_privileges", ["user_id"], :name => "fk_privilege_user" + create_table "user_stories", :force => true do |t| t.integer "product_id" t.integer "priority" @@ -125,6 +141,8 @@ ActiveRecord::Schema.define(:version => 15) do t.datetime "updated_at" end
+ add_index "user_stories", ["product_id"], :name => "fk_user_story_product" + create_table "users", :force => true do |t| t.string "email", :limit => 128, :null => false t.string "display_name", :limit => 128, :null => false diff --git a/test/fixtures/user_verifications.yml b/test/fixtures/user_verifications.yml new file mode 100644 index 0000000..20a7c19 --- /dev/null +++ b/test/fixtures/user_verifications.yml @@ -0,0 +1,6 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +unverified_user_verification: + user_id: <%= Fixtures.identify(:unverified_user) %> + token: <%= UserVerification.create_token %> + sent: <%= Date.today %> diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 6c096e5..590d61b 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -33,4 +33,10 @@ user_admin: email: user_admin@product.com display_name: User admin salt: <%= SALT %> - hashed_password: <%= User.encrypted_password('fungi', SALT) %> \ No newline at end of file + hashed_password: <%= User.encrypted_password('fungi', SALT) %> + +unverified_user: + email: unverified@user.com + display_name: Unverified user + salt: <%= SALT %> + hashed_password: <%= User.encrypted_password('unverified', SALT) %> diff --git a/test/functional/home_controller_test.rb b/test/functional/home_controller_test.rb index b8aaf02..ab8739f 100644 --- a/test/functional/home_controller_test.rb +++ b/test/functional/home_controller_test.rb @@ -24,6 +24,11 @@ class HomeControllerTest < ActionController::TestCase @controller = HomeController.new @authenticated_user = users(:mcpierce) @backlog = BacklogItem.find_all_by_state_and_owner_id(BacklogItem::STATE_ASSIGNED,@authenticated_user.id) + + # setup for testing mail + ActionMailer::Base.delivery_method = :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries = [] end
# Ensures that anonymous users are sent to the login page when they try to @@ -78,34 +83,6 @@ class HomeControllerTest < ActionController::TestCase assert_nil session[:user_id], 'User id was not deleted.' end
- # Ensures that a registration attempt rejects a logged in user. - # - def test_register_for_authenticated_user - get :register, nil, {:user_id => @authenticated_user.id} - - assert_redirected_to :action => :dashboard - end - - # Ensures that registering a new account works as expected. - # - def test_register_create_account - post :register, {:email => 'test@user.com', - :display_name => 'Test User', - :password => 'farkle', - :password_confirmation => 'farkle' } - - assert_redirected_to :action => :login - assert User.find_by_email('test@user.com'), 'User not created' - end - - # Ensure that the registration page works as expected. - # - def test_register - get :register - - assert_response :success - end - # Ensures that the main page doesn't require the user be logged. # def test_index diff --git a/test/functional/user_controller_test.rb b/test/functional/user_controller_test.rb index 584d52b..9812f04 100644 --- a/test/functional/user_controller_test.rb +++ b/test/functional/user_controller_test.rb @@ -1,137 +1,202 @@ # user_controller_test.rb # Copyright (C) 2008, Darryl L. Pierce mcpierce@gmail.com -# +# # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. -# +# # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. -# +# # You should have received a copy of the GNU General Public License along with # this program. If not, see http://www.gnu.org/licenses/. -# +#
require File.dirname(__FILE__) + '/../test_helper'
class UserControllerTest < ActionController::TestCase fixtures :backlog_items + fixtures :user_verifications fixtures :users - + def setup @user = users(:mcpierce) @other_user = users(:jdonuts) @admin = users(:user_admin) - @backlog_items = BacklogItem.find_all_by_owner_id(@user.id) + @backlog_items = BacklogItem.find_all_by_owner_id(@user.id) + + @verified_user = users(:mcpierce) + raise "User should be verified!" unless @verified_user.verified? + + @unverified_user = users(:unverified_user) end - + # Ensures that viewing fails when the user id is missing or invalid. - # + # def test_view_with_invalid_id get :view - + assert_redirected_to error_path - + get :view, {:id => 9999} - + assert_redirected_to error_path end - + # Ensures that viewing a user works as expected. - # + # def test_view get :view, {:id => users(:mcpierce).id} - + assert_response :success assert assigns['this_user'], 'Failed to load the user.' assert assigns['my_backlog'], 'Failed to load the user's backlog.' - assert_equal @backlog_items.size, assigns['my_backlog'].size, + assert_equal @backlog_items.size, assigns['my_backlog'].size, 'Failed to load the right set of items.' end - + # Ensures that viewing a user backlog requires a user id. - # + # def test_backlog_without_id get :backlog - + assert_redirected_to error_path end - + # Ensures that the right backlog is loaded. - # + # def test_backlog get :view, {:id => @user.id} - + assert_response :success assert_equal @backlog_items.size, assigns['my_backlog'].size, 'Did not load the right backlog items.' end - + # Ensures that editing requires a valid user id. - # + # def test_modify_with_invalid_id get :modify, {:id => 9999} - + assert_redirected_to error_path end - + # Ensures that anonymous users cannot modify a user. - # + # def test_modify_as_anonymous get :modify, {:id => @user.id} - + assert_redirected_to :controller => :home, :action => :login end - + # Ensures that other users cannot modify someone's account. - # + # def test_modify_as_someone_else get :modify, {:id => @user.id}, {:user_id => @other_user.id} - + assert_redirected_to error_path end - + # Ensures that an admin can edit a user's account. - # + # def test_modify_as_admin get :modify, {:id => @user.id}, {:user_id => @admin.id} - + assert_response :success end - + # Ensures that a user can edit himself. - # + # def test_modify get :modify, {:id => @user.id}, {:user_id => @user.id} - + assert_response :success end - + # Ensures that saving the updated user details works. # def test_modify email = 'farkle@projxp.com' - + post :modify, {:id => @user.id, :user => {:email => email}}, {:user_id => @user.id} - + assert_redirected_to :action => :view, :id => @user.id - + result = User.find_by_email(email) - + assert result, 'User was not updated.' end + + # Ensures that a user id must be present when verifying a user. + # + def test_verify_without_user_id + get :verify + + assert_redirected_to error_url + end + + # Ensures that the user id must be valid. + # + def test_verify_with_invalid_user_id + get :verify, {:id => 9999} + + assert_redirected_to error_url + end + + # Ensures that a user who has already been verified sees an error message. + # + def test_verify_for_verified_user + get :verify, + {:id => @verified_user.id, :token => UserVerification.create_token} + + assert_redirected_to error_url + end + + # Ensures that a token is required. + # + def test_verify_without_token + get :verify, {:id => @unverified_user.id} + + assert_template "verify" + end + + # Ensures that an invalid token causes verification to fail. + # + def test_verify_with_invalid_token + get :verify, + { + :id => @unverified_user.id, + :token => UserVerification.create_token + } + + assert_template "verify" + end + + # Ensures that verification works as expected. + # + def test_verify + get :verify, + { + :id => @unverified_user.id, + :token => @unverified_user.verification.token + } + + assert_redirected_to :controller => :home, :action => :login + result = User.find_by_id(@unverified_user.id) + assert result.verified?, 'Verification failed to mark the user as verified.' + end end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index 2b12f48..9955734 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -18,15 +18,39 @@ require File.dirname(__FILE__) + '/../test_helper'
class UserTest < ActiveSupport::TestCase + fixtures :users def setup @mcpierce = users(:mcpierce) + + @verified_user = @mcpierce + raise "User should be verified!" unless @verified_user.verified? + @unverified_user = users(:unverified_user) end
# Ensures that authenticating a user using the wrong password fails. # - def authenticate_with_wrong_password + def test_authenticate_with_wrong_password result = User.authenticate(@mcpierce.email, 'klefar')
assert_nil result end + + # Ensures that trying to verify a token for a user with no pending verification + # raises an exception. + # + def test_verify_for_verified_user + assert_raise(Exception) { @verified_user.verify(UserVerification.create_token) } + end + + # Ensures that giving the wrong token does not verified the user. + # + def test_verify_with_wrong_token + flunk "User should not have verified the token." if @unverified_user.verify(UserVerification.create_token) + end + + # Ensures that an unverified user is marke as unverified. + # + def test_verified_with_unverified_user + flunk "User is not verified." if @unverified_user.verified? + end end diff --git a/test/unit/user_verification_test.rb b/test/unit/user_verification_test.rb new file mode 100644 index 0000000..0171729 --- /dev/null +++ b/test/unit/user_verification_test.rb @@ -0,0 +1,77 @@ +# user_verification_test.rb +# Copyright (C) 2008, Darryl L. Pierce mcpierce@gmail.com +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see http://www.gnu.org/licenses/. +# + +require File.dirname(__FILE__) + '/../test_helper' + +class UserVerificationTest < ActiveSupport::TestCase + + def setup + @verification = UserVerification.new( + :user_id => 1, + :token => UserVerification.create_token, + :sent => Date.today) + end + + # Ensures that a verification requires an associated user. + # + def test_valid_fails_without_user_id + @verification.user_id = nil + + flunk "A user must be specified." if @verification.valid? + end + + # Ensures that a verification token is required. + # + def test_valid_fails_without_token + @verification.token = nil + + flunk "A token must be specified." if @verification.valid? + end + + # Ensures that a verification token must be 16 alpha-numeric characters. + # + def test_valid_fails_with_invalid_token + @verification.token = "this is invalid!" + + flunk "Tokens must be 16 alpha-numeric characters only." if @verification.valid? + end + + # Ensures that a sent date has to be set. + # + def test_valid_fails_without_sent_date + @verification.sent = nil + + flunk "Verification must have a sent date." if @verification.valid? + end + + # Ensures taht a well-formed verification is considered valid. + # + def test_valid + flunk "There is a problem in validation." unless @verification.valid? + end + + # Ensures that the token generator always creates a 16-character alphanumeric + # token. + # + def test_create_token + (1..100).each do |num| + token = UserVerification.create_token + + flunk "The token generator is broken." unless token =~ /#{UserVerification::TOKEN_PATTERN}/ + end + end +end
ACK - Works great ;-). But just a suggestion: I think it's a good idea to put a flash message at line 76 of user_controller.rb that say "Validation mail sended".
-- Benjamin LAN-SUN-LUK
Le 26/10/08 06:03, « Darryl L. Pierce » mcpierce@gmail.com a écrit :
It also includes a new controller which handles verifying the user after they follow the link in the email.
You will need to run a migration with this patch.
To set the hostname for where the user goes to verify themselves, update the config/environments/{development, test, production}.rb file and enter the correct public hostname.
It introduces a new model object, UserVerification. When a user registers an account, an instance is created. As long as that object exists, the user cannot do anything with the system.
_______________________________________________ projxp-devel mailing list projxp-devel@lists.fedorahosted.org https://fedorahosted.org/mailman/listinfo/projxp-devel
On Sun, Oct 26, 2008 at 9:27 AM, LAN-SUN-LUK Benjamin Benjamin.LAN-SUN-LUK@supinfo.com wrote:
ACK – Works great ;-). But just a suggestion: I think it's a good idea to put a flash message at line 76 of user_controller.rb that say "Validation mail sended".
Good suggestion. I'll add one and push.
projxp-devel@lists.fedorahosted.org