login_attempts.diff

Alexander J. Murmann, 2009-04-27 04:26

Download (13.8 KB)

View differences:

/home/amurmann/workspace/redmine/app/controllers/account_controller.rb (working copy)
141 141
    token = Token.find_by_action_and_value('register', params[:token])
142 142
    redirect_to(home_url) && return unless token and !token.expired?
143 143
    user = token.user
144
    redirect_to(home_url) && return unless user.status == User::STATUS_REGISTERED
144
    redirect_to(home_url) && return unless user.status == User::STATUS_REGISTERED || user.status == User::STATUS_LOCKED
145 145
    user.status = User::STATUS_ACTIVE
146
    user.failed_logins = 0
146 147
    if user.save
147 148
      token.destroy
148 149
      flash[:notice] = l(:notice_account_activated)
......
155 156
  def password_authentication
156 157
    user = User.try_to_login(params[:username], params[:password])
157 158
    if user.nil?
158
      # Invalid credentials
159
      flash.now[:error] = l(:notice_account_invalid_creditentials)
159
      # Invalid credentials      
160
      flash.now[:error] = remaining_login_attempts_notice params[:username]
160 161
    elsif user.new_record?
161 162
      # Onthefly creation failed, display the registration form to fill/fix attributes
162 163
      @user = user
......
210 211
    end
211 212
  end
212 213
  
213
  def successful_authentication(user)
214
  def successful_authentication(user)    
214 215
    # Valid user
215 216
    self.logged_user = user
217
    # Reset count for failed authentication attempts
218
    user.failed_logins = 0
219
    user.save
216 220
    # generate a key and set cookie if autologin
217 221
    if params[:autologin] && Setting.autologin?
218 222
      token = Token.create(:user => user, :action => 'autologin')
......
276 280
    flash[:notice] = l(:notice_account_pending)
277 281
    redirect_to :action => 'login'
278 282
  end
283
  
284
  def remaining_login_attempts_notice(user_name)
285
      user = User.find_by_login(user_name)
286
      allowed_attempts = Setting.allowed_login_attempts.to_i
287
      return l(:notice_account_invalid_creditentials) if user.nil? || allowed_attempts == 0
288
      if allowed_attempts - user.failed_logins > 0
289
        s = l(:notice_account_invalid_creditentials)
290
        s << " "
291
        s << l(:notice_remaining_login_attempts, :value => (allowed_attempts - user.failed_logins))
292
      else
293
        s = l(:notice_too_many_login_attempts)
294
      end
295
    s 
296
  end
297
  
279 298
end
/home/amurmann/workspace/redmine/app/models/mailer.rb (working copy)
215 215
    subject 'Redmine test'
216 216
    body :url => url_for(:controller => 'welcome')
217 217
  end
218

  
218
  
219
  def account_locked(token, user)
220
    set_language_if_valid(user.language)
221
    recipients user.mail
222
    subject l(:mail_subject_account_locked, :value => Setting.app_title)
223
    body :project => Setting.app_title, :token => token,
224
         :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
225
  end
226
    
227
  def admin_info_locked_account(user, admin_mail)
228
    recipients admin_mail
229
    subject l(:mail_subject_admin_info_locked_account, :value => user.login)
230
    body :value => user.login
231
  end
232
  
219 233
  # Overrides default deliver! method to prevent from sending an email
220 234
  # with no recipient, cc or bcc
221 235
  def deliver!(mail = @mail)
/home/amurmann/workspace/redmine/app/models/user.rb (working copy)
50 50
  attr_accessor :password, :password_confirmation
51 51
  attr_accessor :last_before_login_on
52 52
  # Prevents unauthorized assignments
53
  attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
53
  attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :failed_logins
54 54
	
55 55
  validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
56 56
  validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
......
101 101
    if user
102 102
      # user is already in local database
103 103
      return nil if !user.active?
104
      if user.auth_source
104
      if user.auth_source        
105 105
        # user has an external authentication method
106
        return nil unless user.auth_source.authenticate(login, password)
106
        if !user.auth_source.authenticate(login, password)        
107
          user.authentication_failed user
108
          return nil
109
        end
107 110
      else
108 111
        # authentication with local password
109
        return nil unless User.hash_password(password) == user.hashed_password        
112
        if User.hash_password(password) != user.hashed_password
113
          user.authentication_failed
114
          return nil
115
        end       
110 116
      end
111 117
    else
112 118
      # user is not yet registered, try to authenticate with available sources
......
127 133
    raise text
128 134
  end
129 135
  
136
  def authentication_failed
137
    self.failed_logins = self.failed_logins + 1
138
    if self.failed_logins >= Setting.allowed_login_attempts.to_i && Setting.allowed_login_attempts != 0
139
      self.status = 3
140
      token = Token.new(:user => self, :action => "register")
141
      token.save     
142
      Mailer.deliver_account_locked(token, self)
143
      if !Setting.too_many_logins_email.empty?
144
        logger.info("Sending admin mail.")
145
        Mailer.deliver_admin_info_locked_account(self, Setting.too_many_logins_email)
146
      end
147
    end    
148
    self.save
149
  end
150
  
130 151
  # Returns the user who matches the given autologin +key+ or nil
131 152
  def self.try_to_autologin(key)
132 153
    token = Token.find_by_action_and_value('autologin', key)
/home/amurmann/workspace/redmine/app/views/mailer/account_locked.text.html.rhtml (revision 0)
1
<p><%= l(:mail_body_account_locked, :value => @project) %></p>
2
<%= auto_link(@url) %></p>
/home/amurmann/workspace/redmine/app/views/mailer/account_locked.text.plain.rhtml (revision 0)
1
<%= l(:mail_body_account_locked, :value => @project) %>
2
<%= auto_link(@url) %>
/home/amurmann/workspace/redmine/app/views/mailer/admin_info_locked_account.html.rhtml (revision 0)
1
<p><%= l(:mail_body_admin_info_locked_account, :value => @value) %></p>
/home/amurmann/workspace/redmine/app/views/mailer/admin_info_locked_account.plain.rhtml (revision 0)
1
<%= l(:mail_body_admin_info_locked_account, :value => @value) %>
/home/amurmann/workspace/redmine/app/views/settings/_authentication.rhtml (working copy)
20 20

  
21 21
<p><label><%= l(:setting_openid) %></label>
22 22
<%= check_box_tag 'settings[openid]', 1, Setting.openid?, :disabled => !Object.const_defined?(:OpenID) %><%= hidden_field_tag 'settings[openid]', 0 %></p>
23

  
24
<p><label><%= l(:label_allowed_login_attempts) %></label>
25
<%= text_field_tag 'settings[allowed_login_attempts]', Setting.allowed_login_attempts, :size => 6%><br /><em><%= l(:text_login_attempts) %></em></p>
26

  
27
<p><label><%= l(:label_too_many_logins_email) %></label>
28
<%= text_field_tag 'settings[too_many_logins_email]', Setting.too_many_logins_email, :size => 60%><br /><em><%= l(:text_account_locked_notification_address) %></em></p>
23 29
</div>
24 30

  
25 31
<div style="float:right;">
/home/amurmann/workspace/redmine/config/locales/en.yml (working copy)
107 107
  general_first_day_of_week: '7'
108 108
  
109 109
  notice_account_updated: Account was successfully updated.
110
  notice_account_invalid_creditentials: Invalid user or password
110
  notice_account_invalid_creditentials: Invalid user or password.
111 111
  notice_account_password_updated: Password was successfully updated.
112 112
  notice_account_wrong_password: Wrong password
113 113
  notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
......
130 130
  notice_account_pending: "Your account was created and is now pending administrator approval."
131 131
  notice_default_data_loaded: Default configuration successfully loaded.
132 132
  notice_unable_delete_version: Unable to delete version.
133
  notice_remaining_login_attempts: "{{value}} login attempt(s) left."
134
  notice_too_many_login_attempts: "Too many login attempts. Your account has been locked and an administrator has been informed."
133 135
  
134 136
  error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}"
135 137
  error_scm_not_found: "The entry or revision was not found in the repository."
......
149 151
  mail_body_account_activation_request: "A new user ({{value}}) has registered. The account is pending your approval:"
150 152
  mail_subject_reminder: "{{count}} issue(s) due in the next days"
151 153
  mail_body_reminder: "{{count}} issue(s) that are assigned to you are due in the next {{days}} days:"
154
  mail_subject_account_locked: "Your {{value}} account has been locked"
155
  mail_body_account_locked: "You or someone else attempted too often to log in to your {{value}} account. Your account has been locked to protect you and your project. To reactivate your account, click on the following link: "
156
  mail_body_admin_info_locked_account: "Due to too many failed login attempts, the account {{value}} has been locked."
157
  mail_subject_admin_info_locked_account: "Account {{value}} locked."
152 158
  
153 159
  gui_validation_error: 1 error
154 160
  gui_validation_error_plural: "{{count}} errors"
......
668 674
  label_ascending: Ascending
669 675
  label_descending: Descending
670 676
  label_date_from_to: From {{start}} to {{end}}
677
  label_allowed_login_attempts: Allowed login attempts
678
  label_too_many_logins_email: Notify admin if locked
671 679
  
672 680
  button_login: Login
673 681
  button_submit: Submit
......
761 769
  text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
762 770
  text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
763 771
  text_custom_field_possible_values_info: 'One line for each value'
772
  text_login_attempts: 'Put 0 to allow unlimited login attempts.'
773
  text_account_locked_notification_address: "Set an administrator's email address to be notified if an account was locked because of too many login attempts.\nLeave this empty to notify noone."
764 774
  
765 775
  default_role_manager: Manager
766 776
  default_role_developper: Developer
/home/amurmann/workspace/redmine/config/settings.yml (working copy)
150 150
  default: 0
151 151
openid:
152 152
  default: 0
153
allowed_login_attempts:
154
  default: 0
155
too_many_logins_email:
156
  default: ''
/home/amurmann/workspace/redmine/db/migrate/20090405025700_add_failed_logins_to_users.rb (revision 0)
1
class AddFailedLoginsToUsers < ActiveRecord::Migration
2
  def self.up
3
    add_column :users, :failed_logins, :integer, :default => 0
4
  end
5

  
6
  def self.down
7
    remove_column :users, :failed_logins
8
  end
9
end
/home/amurmann/workspace/redmine/test/unit/user_test.rb (working copy)
95 95
    assert_equal User.hash_password("hello"), user.hashed_password    
96 96
  end
97 97
  
98
  def test_login_attempts
99
    Setting.allowed_login_attempts = 2
100
    user = User.try_to_login("admin", "wrong")
101
    assert_equal nil, user
102
    user = User.try_to_login("admin", "admin")
103
    assert_equal "admin", user.login
104
    
105
    user = User.try_to_login("admin", "wrong")
106
    user = User.try_to_login("admin", "wrong")
107
    user = User.try_to_login("admin", "admin")
108
    assert_equal nil, user   
109
  end
110
  
98 111
  def test_name_format
99 112
    assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
100 113
    Setting.user_format = :firstname_lastname