Index: /home/amurmann/workspace/redmine/app/controllers/account_controller.rb =================================================================== --- /home/amurmann/workspace/redmine/app/controllers/account_controller.rb (revision 2648) +++ /home/amurmann/workspace/redmine/app/controllers/account_controller.rb (working copy) @@ -141,8 +141,9 @@ token = Token.find_by_action_and_value('register', params[:token]) redirect_to(home_url) && return unless token and !token.expired? user = token.user - redirect_to(home_url) && return unless user.status == User::STATUS_REGISTERED + redirect_to(home_url) && return unless user.status == User::STATUS_REGISTERED || user.status == User::STATUS_LOCKED user.status = User::STATUS_ACTIVE + user.failed_logins = 0 if user.save token.destroy flash[:notice] = l(:notice_account_activated) @@ -155,8 +156,8 @@ def password_authentication user = User.try_to_login(params[:username], params[:password]) if user.nil? - # Invalid credentials - flash.now[:error] = l(:notice_account_invalid_creditentials) + # Invalid credentials + flash.now[:error] = remaining_login_attempts_notice params[:username] elsif user.new_record? # Onthefly creation failed, display the registration form to fill/fix attributes @user = user @@ -210,9 +211,12 @@ end end - def successful_authentication(user) + def successful_authentication(user) # Valid user self.logged_user = user + # Reset count for failed authentication attempts + user.failed_logins = 0 + user.save # generate a key and set cookie if autologin if params[:autologin] && Setting.autologin? token = Token.create(:user => user, :action => 'autologin') @@ -276,4 +280,19 @@ flash[:notice] = l(:notice_account_pending) redirect_to :action => 'login' end + + def remaining_login_attempts_notice(user_name) + user = User.find_by_login(user_name) + allowed_attempts = Setting.allowed_login_attempts.to_i + return l(:notice_account_invalid_creditentials) if user.nil? || allowed_attempts == 0 + if allowed_attempts - user.failed_logins > 0 + s = l(:notice_account_invalid_creditentials) + s << " " + s << l(:notice_remaining_login_attempts, :value => (allowed_attempts - user.failed_logins)) + else + s = l(:notice_too_many_login_attempts) + end + s + end + end Index: /home/amurmann/workspace/redmine/app/models/mailer.rb =================================================================== --- /home/amurmann/workspace/redmine/app/models/mailer.rb (revision 2648) +++ /home/amurmann/workspace/redmine/app/models/mailer.rb (working copy) @@ -215,7 +215,21 @@ subject 'Redmine test' body :url => url_for(:controller => 'welcome') end - + + def account_locked(token, user) + set_language_if_valid(user.language) + recipients user.mail + subject l(:mail_subject_account_locked, :value => Setting.app_title) + body :project => Setting.app_title, :token => token, + :url => url_for(:controller => 'account', :action => 'activate', :token => token.value) + end + + def admin_info_locked_account(user, admin_mail) + recipients admin_mail + subject l(:mail_subject_admin_info_locked_account, :value => user.login) + body :value => user.login + end + # Overrides default deliver! method to prevent from sending an email # with no recipient, cc or bcc def deliver!(mail = @mail) Index: /home/amurmann/workspace/redmine/app/models/user.rb =================================================================== --- /home/amurmann/workspace/redmine/app/models/user.rb (revision 2648) +++ /home/amurmann/workspace/redmine/app/models/user.rb (working copy) @@ -50,7 +50,7 @@ attr_accessor :password, :password_confirmation attr_accessor :last_before_login_on # Prevents unauthorized assignments - attr_protected :login, :admin, :password, :password_confirmation, :hashed_password + attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :failed_logins validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) } validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? } @@ -101,12 +101,18 @@ if user # user is already in local database return nil if !user.active? - if user.auth_source + if user.auth_source # user has an external authentication method - return nil unless user.auth_source.authenticate(login, password) + if !user.auth_source.authenticate(login, password) + user.authentication_failed user + return nil + end else # authentication with local password - return nil unless User.hash_password(password) == user.hashed_password + if User.hash_password(password) != user.hashed_password + user.authentication_failed + return nil + end end else # user is not yet registered, try to authenticate with available sources @@ -127,6 +133,22 @@ raise text end + def authentication_failed + self.failed_logins = self.failed_logins + 1 + if self.failed_logins >= Setting.allowed_login_attempts.to_i && Setting.allowed_login_attempts != 0 + self.status = 3 + token = Token.new(:user => self, :action => "register") + self.save! + token.save + Mailer.deliver_account_locked(token, self) + if !Setting.too_many_logins_email.empty? + logger.info("Sending admin mail.") + Mailer.deliver_admin_info_locked_account(self, Setting.too_many_logins_email) + end + end + self.save! + end + # Returns the user who matches the given autologin +key+ or nil def self.try_to_autologin(key) token = Token.find_by_action_and_value('autologin', key) Index: /home/amurmann/workspace/redmine/app/views/mailer/account_locked.text.html.rhtml =================================================================== --- /home/amurmann/workspace/redmine/app/views/mailer/account_locked.text.html.rhtml (revision 0) +++ /home/amurmann/workspace/redmine/app/views/mailer/account_locked.text.html.rhtml (revision 0) @@ -0,0 +1,2 @@ +
<%= l(:mail_body_account_locked, :value => @project) %>
+<%= auto_link(@url) %> Index: /home/amurmann/workspace/redmine/app/views/mailer/account_locked.text.plain.rhtml =================================================================== --- /home/amurmann/workspace/redmine/app/views/mailer/account_locked.text.plain.rhtml (revision 0) +++ /home/amurmann/workspace/redmine/app/views/mailer/account_locked.text.plain.rhtml (revision 0) @@ -0,0 +1,2 @@ +<%= l(:mail_body_account_locked, :value => @project) %> +<%= auto_link(@url) %> Index: /home/amurmann/workspace/redmine/app/views/mailer/admin_info_locked_account.html.rhtml =================================================================== --- /home/amurmann/workspace/redmine/app/views/mailer/admin_info_locked_account.html.rhtml (revision 0) +++ /home/amurmann/workspace/redmine/app/views/mailer/admin_info_locked_account.html.rhtml (revision 0) @@ -0,0 +1 @@ +<%= l(:mail_body_admin_info_locked_account, :value => @value) %>
Index: /home/amurmann/workspace/redmine/app/views/mailer/admin_info_locked_account.plain.rhtml =================================================================== --- /home/amurmann/workspace/redmine/app/views/mailer/admin_info_locked_account.plain.rhtml (revision 0) +++ /home/amurmann/workspace/redmine/app/views/mailer/admin_info_locked_account.plain.rhtml (revision 0) @@ -0,0 +1 @@ +<%= l(:mail_body_admin_info_locked_account, :value => @value) %> Index: /home/amurmann/workspace/redmine/app/views/settings/_authentication.rhtml =================================================================== --- /home/amurmann/workspace/redmine/app/views/settings/_authentication.rhtml (revision 2648) +++ /home/amurmann/workspace/redmine/app/views/settings/_authentication.rhtml (working copy) @@ -20,6 +20,12 @@<%= check_box_tag 'settings[openid]', 1, Setting.openid?, :disabled => !Object.const_defined?(:OpenID) %><%= hidden_field_tag 'settings[openid]', 0 %>
+ +
+<%= text_field_tag 'settings[allowed_login_attempts]', Setting.allowed_login_attempts, :size => 6%>
<%= l(:text_login_attempts) %>
+<%= text_field_tag 'settings[too_many_logins_email]', Setting.too_many_logins_email, :size => 60%>
<%= l(:text_account_locked_notification_address) %>