]> source.dussan.org Git - redmine.git/commitdiff
Option to generate a random password on user creation/update.
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 23 Feb 2013 16:50:07 +0000 (16:50 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 23 Feb 2013 16:50:07 +0000 (16:50 +0000)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@11456 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/controllers/users_controller.rb
app/models/user.rb
app/views/users/_form.html.erb
config/locales/en.yml
config/locales/fr.yml
test/functional/users_controller_test.rb
test/unit/user_test.rb

index 23a979ea29ea9eabede90ba6e6ff6a5f72684f29..2cc43919ca415c60ce584cf66ac15a8d7cf4f4b0 100644 (file)
@@ -80,6 +80,7 @@ class UsersController < ApplicationController
 
   def new
     @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
+    @user.safe_attributes = params[:user]
     @auth_sources = AuthSource.all
   end
 
@@ -96,13 +97,14 @@ class UsersController < ApplicationController
       @user.pref.save
       @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : [])
 
-      Mailer.account_information(@user, params[:user][:password]).deliver if params[:send_information]
+      Mailer.account_information(@user, @user.password).deliver if params[:send_information]
 
       respond_to do |format|
         format.html {
           flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
           if params[:continue]
-            redirect_to new_user_path
+            attrs = params[:user].slice(:generate_password)
+            redirect_to new_user_path(:user => attrs)
           else
             redirect_to edit_user_path(@user)
           end
@@ -145,8 +147,8 @@ class UsersController < ApplicationController
 
       if was_activated
         Mailer.account_activated(@user).deliver
-      elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
-        Mailer.account_information(@user, params[:user][:password]).deliver
+      elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil?
+        Mailer.account_information(@user, @user.password).deliver
       end
 
       respond_to do |format|
index 3670ad2a1dcb2dfa8b1eae20b59e0b79c8acfdbd..3acd4bbe2813bdc054cb98bde8eebbad71626d73 100644 (file)
@@ -81,7 +81,7 @@ class User < Principal
 
   acts_as_customizable
 
-  attr_accessor :password, :password_confirmation
+  attr_accessor :password, :password_confirmation, :generate_password
   attr_accessor :last_before_login_on
   # Prevents unauthorized assignments
   attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
@@ -103,7 +103,7 @@ class User < Principal
   validate :validate_password_length
 
   before_create :set_mail_notification
-  before_save   :update_hashed_password
+  before_save   :generate_password_if_needed, :update_hashed_password
   before_destroy :remove_references_before_destroy
 
   scope :in_group, lambda {|group|
@@ -274,13 +274,16 @@ class User < Principal
     return auth_source.allow_password_changes?
   end
 
-  # Generate and set a random password.  Useful for automated user creation
-  # Based on Token#generate_token_value
-  #
-  def random_password
+  def generate_password?
+    generate_password == '1' || generate_password == true
+  end
+
+  # Generate and set a random password on given length
+  def random_password(length=40)
     chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+    chars -= %w(0 O 1 l)
     password = ''
-    40.times { |i| password << chars[rand(chars.size-1)] }
+    length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
     self.password = password
     self.password_confirmation = password
     self
@@ -541,6 +544,7 @@ class User < Principal
 
   safe_attributes 'status',
     'auth_source_id',
+    'generate_password',
     :if => lambda {|user, current_user| current_user.admin?}
 
   safe_attributes 'group_ids',
@@ -610,6 +614,7 @@ class User < Principal
   protected
 
   def validate_password_length
+    return if password.blank? && generate_password?
     # Password length validation based on setting
     if !password.nil? && password.size < Setting.password_min_length.to_i
       errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
@@ -618,6 +623,13 @@ class User < Principal
 
   private
 
+  def generate_password_if_needed
+    if generate_password? && auth_source.nil?
+      length = [Setting.password_min_length.to_i + 2, 10].max
+      random_password(length)
+    end
+  end
+
   # Removes references that are not handled by associations
   # Things that are not deleted are reassociated with the anonymous user
   def remove_references_before_destroy
index ddc826fa875d6d3b0133eb5a15c8037077ba1962..57c2f29b25c6c49a76d05818cc02369418213391 100644 (file)
@@ -28,6 +28,7 @@
   <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 %>
   <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  %>
   <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>
 </div>
 <div style="clear:left;"></div>
 <!--[eoform:user]-->
+
+<%= javascript_tag do %>
+$(document).ready(function(){
+  $('#user_generate_password').change(function(){
+    var passwd = $('#user_password, #user_password_confirmation');
+    if ($(this).is(':checked')){
+      passwd.val('').attr('disabled', true);
+    }else{
+      passwd.removeAttr('disabled');
+    }
+  }).trigger('change');
+});
+<% end %>
index 39583b583d583576ad8445761476ffbc1cb61de6..4dcc3defd60130cf5072eaeae2b86ea56d24bcb1 100644 (file)
@@ -331,6 +331,7 @@ en:
   field_board_parent: Parent forum
   field_private_notes: Private notes
   field_inherit_members: Inherit members
+  field_generate_password: Generate password
 
   setting_app_title: Application title
   setting_app_subtitle: Application subtitle
index d9d79938bb0c4cfd90a05f637490f38f0e84883c..9d74f31ec2bc80a075a50930fee8d1384ad34a1b 100644 (file)
@@ -331,6 +331,7 @@ fr:
   field_board_parent: Forum parent
   field_private_notes: Notes privées
   field_inherit_members: Hériter les membres
+  field_generate_password: Générer un mot de passe
 
   setting_app_title: Titre de l'application
   setting_app_subtitle: Sous-titre de l'application
index 37e58d5005628bb2ad8eab6a777c9097b54a292e..65aa2d7c0885fade87abaabc6b70bd485d60597d 100644 (file)
@@ -218,6 +218,30 @@ class UsersControllerTest < ActionController::TestCase
     assert_equal '0', user.pref[:warn_on_leaving_unsaved]
   end
 
+  def test_create_with_generate_password_should_email_the_password
+    assert_difference 'User.count' do
+      post :create, :user => {
+        :login => 'randompass',
+        :firstname => 'Random',
+        :lastname => 'Pass',
+        :mail => 'randompass@example.net',
+        :language => 'en',
+        :generate_password => '1',
+        :password => '',
+        :password_confirmation => ''
+      }, :send_information => 1
+    end
+    user = User.order('id DESC').first
+    assert_equal 'randompass', user.login
+
+    mail = ActionMailer::Base.deliveries.last
+    assert_not_nil mail
+    m = mail_body(mail).match(/Password: ([a-zA-Z0-9]+)/)
+    assert m
+    password = m[1]
+    assert user.check_password?(password)
+  end
+
   def test_create_with_failure
     assert_no_difference 'User.count' do
       post :create, :user => {}
@@ -290,6 +314,37 @@ class UsersControllerTest < ActionController::TestCase
     assert_mail_body_match 'newpass123', mail
   end
 
+  def test_update_with_generate_password_should_email_the_password
+    ActionMailer::Base.deliveries.clear
+    Setting.bcc_recipients = '1'
+
+    put :update, :id => 2, :user => {
+      :generate_password => '1',
+      :password => '',
+      :password_confirmation => ''
+    }, :send_information => '1'
+
+    mail = ActionMailer::Base.deliveries.last
+    assert_not_nil mail
+    m = mail_body(mail).match(/Password: ([a-zA-Z0-9]+)/)
+    assert m
+    password = m[1]
+    assert User.find(2).check_password?(password)
+  end
+
+  def test_update_without_generate_password_should_not_change_password
+    put :update, :id => 2, :user => {
+      :firstname => 'changed',
+      :generate_password => '0',
+      :password => '',
+      :password_confirmation => ''
+    }, :send_information => '1'
+
+    user = User.find(2)
+    assert_equal 'changed', user.firstname
+    assert user.check_password?('jsmith')
+  end
+
   def test_update_user_switchin_from_auth_source_to_password_authentication
     # Configure as auth source
     u = User.find(2)
index 28d09518eb90d162d50c70606543aaaa8301b2a9..9ea57c72226aa8804ac43cb3f329f5b8a3250274 100644 (file)
@@ -70,6 +70,27 @@ class UserTest < ActiveSupport::TestCase
     assert user.save
   end
 
+  def test_generate_password_on_create_should_set_password
+    user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
+    user.login = "newuser"
+    user.generate_password = true
+    assert user.save
+
+    password = user.password
+    assert user.check_password?(password)
+  end
+
+  def test_generate_password_on_update_should_update_password
+    user = User.find(2)
+    hash = user.hashed_password
+    user.generate_password = true
+    assert user.save
+
+    password = user.password
+    assert user.check_password?(password)
+    assert_not_equal hash, user.reload.hashed_password
+  end
+
   def test_create
     user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")