Option to force a user to change his password (#3872).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@12081 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2013-08-05 17:58:33 +00:00
parent bd4fba08e5
commit b764e39847
10 changed files with 78 additions and 6 deletions

View File

@ -20,7 +20,7 @@ class AccountController < ApplicationController
include CustomFieldsHelper include CustomFieldsHelper
# prevents login action to be filtered by check_if_login_required application scope filter # prevents login action to be filtered by check_if_login_required application scope filter
skip_before_filter :check_if_login_required skip_before_filter :check_if_login_required, :check_password_change
# Login request and validation # Login request and validation
def login def login

View File

@ -38,7 +38,7 @@ class ApplicationController < ActionController::Base
cookies.delete(autologin_cookie_name) cookies.delete(autologin_cookie_name)
end end
before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization before_filter :session_expiration, :user_setup, :check_if_login_required, :check_password_change, :set_localization
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
rescue_from ::Unauthorized, :with => :deny_access rescue_from ::Unauthorized, :with => :deny_access
@ -78,6 +78,9 @@ class ApplicationController < ActionController::Base
session[:user_id] = user.id session[:user_id] = user.id
session[:ctime] = Time.now.utc.to_i session[:ctime] = Time.now.utc.to_i
session[:atime] = Time.now.utc.to_i session[:atime] = Time.now.utc.to_i
if user.must_change_password?
session[:pwd] = '1'
end
end end
def user_setup def user_setup
@ -112,6 +115,10 @@ class ApplicationController < ActionController::Base
authenticate_with_http_basic do |username, password| authenticate_with_http_basic do |username, password|
user = User.try_to_login(username, password) || User.find_by_api_key(username) user = User.try_to_login(username, password) || User.find_by_api_key(username)
end end
if user && user.must_change_password?
render_error :message => 'You must change your password', :status => 403
return
end
end end
# Switch user if requested by an admin user # Switch user if requested by an admin user
if user && user.admin? && (username = api_switch_user_from_request) if user && user.admin? && (username = api_switch_user_from_request)
@ -170,6 +177,16 @@ class ApplicationController < ActionController::Base
require_login if Setting.login_required? require_login if Setting.login_required?
end end
def check_password_change
if session[:pwd]
if User.current.must_change_password?
redirect_to my_password_path
else
session.delete(:pwd)
end
end
end
def set_localization def set_localization
lang = nil lang = nil
if User.current.logged? if User.current.logged?

View File

@ -17,6 +17,8 @@
class MyController < ApplicationController class MyController < ApplicationController
before_filter :require_login before_filter :require_login
# let user change his password when he has to
skip_before_filter :check_password_change, :only => :password
helper :issues helper :issues
helper :users helper :users
@ -90,14 +92,17 @@ class MyController < ApplicationController
return return
end end
if request.post? if request.post?
if @user.check_password?(params[:password]) if !@user.check_password?(params[:password])
flash.now[:error] = l(:notice_account_wrong_password)
elsif params[:password] == params[:new_password]
flash.now[:error] = 'Your new password must be different from your current password'
else
@user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
@user.must_change_passwd = false
if @user.save if @user.save
flash[:notice] = l(:notice_account_password_updated) flash[:notice] = l(:notice_account_password_updated)
redirect_to my_account_path redirect_to my_account_path
end end
else
flash[:error] = l(:notice_account_wrong_password)
end end
end end
end end

View File

@ -280,6 +280,10 @@ class User < Principal
return auth_source.allow_password_changes? return auth_source.allow_password_changes?
end end
def must_change_password?
must_change_passwd? && change_password_allowed?
end
def generate_password? def generate_password?
generate_password == '1' || generate_password == true generate_password == '1' || generate_password == true
end end
@ -568,6 +572,7 @@ class User < Principal
safe_attributes 'status', safe_attributes 'status',
'auth_source_id', 'auth_source_id',
'generate_password', 'generate_password',
'must_change_passwd',
:if => lambda {|user, current_user| current_user.admin?} :if => lambda {|user, current_user| current_user.admin?}
safe_attributes 'group_ids', safe_attributes 'group_ids',

View File

@ -17,6 +17,10 @@
<%= submit_tag l(:button_apply) %> <%= submit_tag l(:button_apply) %>
<% end %> <% end %>
<% unless @user.must_change_passwd? %>
<% content_for :sidebar do %> <% content_for :sidebar do %>
<%= render :partial => 'sidebar' %> <%= render :partial => 'sidebar' %>
<% end %> <% end %>
<% end %>
<%= javascript_tag "$('#password').focus();" %>

View File

@ -28,10 +28,11 @@
<p><%= f.select :auth_source_id, ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }), {}, :onchange => "if (this.value=='') {$('#password_fields').show();} else {$('#password_fields').hide();}" %></p> <p><%= f.select :auth_source_id, ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }), {}, :onchange => "if (this.value=='') {$('#password_fields').show();} else {$('#password_fields').hide();}" %></p>
<% end %> <% end %>
<div id="password_fields" style="<%= 'display:none;' if @user.auth_source %>"> <div id="password_fields" style="<%= 'display:none;' if @user.auth_source %>">
<p><%= f.check_box :generate_password %></p>
<p><%= f.password_field :password, :required => true, :size => 25 %> <p><%= f.password_field :password, :required => true, :size => 25 %>
<em class="info"><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em></p> <em class="info"><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em></p>
<p><%= f.password_field :password_confirmation, :required => true, :size => 25 %></p> <p><%= f.password_field :password_confirmation, :required => true, :size => 25 %></p>
<p><%= f.check_box :generate_password %></p>
<p><%= f.check_box :must_change_passwd %></p>
</div> </div>
</fieldset> </fieldset>
</div> </div>

View File

@ -335,6 +335,7 @@ en:
field_private_notes: Private notes field_private_notes: Private notes
field_inherit_members: Inherit members field_inherit_members: Inherit members
field_generate_password: Generate password field_generate_password: Generate password
field_must_change_passwd: Must change password at next logon
setting_app_title: Application title setting_app_title: Application title
setting_app_subtitle: Application subtitle setting_app_subtitle: Application subtitle

View File

@ -335,6 +335,7 @@ fr:
field_private_notes: Notes privées field_private_notes: Notes privées
field_inherit_members: Hériter les membres field_inherit_members: Hériter les membres
field_generate_password: Générer un mot de passe field_generate_password: Générer un mot de passe
field_must_change_passwd: Doit changer de mot de passe à la prochaine connexion
setting_app_title: Titre de l'application setting_app_title: Titre de l'application
setting_app_subtitle: Sous-titre de l'application setting_app_subtitle: Sous-titre de l'application

View File

@ -0,0 +1,9 @@
class AddUsersMustChangePasswd < ActiveRecord::Migration
def up
add_column :users, :must_change_passwd, :boolean, :default => false, :null => false
end
def down
remove_column :users, :must_change_passwd
end
end

View File

@ -128,6 +128,35 @@ class AccountTest < ActionController::IntegrationTest
assert_equal 0, Token.count assert_equal 0, Token.count
end end
def test_user_with_must_change_passwd_should_be_forced_to_change_its_password
User.find_by_login('jsmith').update_attribute :must_change_passwd, true
post '/login', :username => 'jsmith', :password => 'jsmith'
assert_redirected_to '/my/page'
follow_redirect!
assert_redirected_to '/my/password'
get '/issues'
assert_redirected_to '/my/password'
end
def test_user_with_must_change_passwd_should_be_able_to_change_its_password
User.find_by_login('jsmith').update_attribute :must_change_passwd, true
post '/login', :username => 'jsmith', :password => 'jsmith'
assert_redirected_to '/my/page'
follow_redirect!
assert_redirected_to '/my/password'
follow_redirect!
assert_response :success
post '/my/password', :password => 'jsmith', :new_password => 'newpassword', :new_password_confirmation => 'newpassword'
assert_redirected_to '/my/account'
follow_redirect!
assert_response :success
assert_equal false, User.find_by_login('jsmith').must_change_passwd?
end
def test_register_with_automatic_activation def test_register_with_automatic_activation
Setting.self_registration = '3' Setting.self_registration = '3'