Feature #3096 » login_attempts.diff
/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 |
self.save! |
|
142 |
token.save |
|
143 |
Mailer.deliver_account_locked(token, self) |
|
144 |
if !Setting.too_many_logins_email.empty? |
|
145 |
logger.info("Sending admin mail.") |
|
146 |
Mailer.deliver_admin_info_locked_account(self, Setting.too_many_logins_email) |
|
147 |
end |
|
148 |
end |
|
149 |
self.save! |
|
150 |
end |
|
151 |
|
|
130 | 152 |
# Returns the user who matches the given autologin +key+ or nil |
131 | 153 |
def self.try_to_autologin(key) |
132 | 154 |
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 |