From 72cb2bc73e71f54c2223bb78af29fee888590b53 Mon Sep 17 00:00:00 2001 From: "mkanat%bugzilla.org" <> Date: Sun, 13 Dec 2009 20:46:24 +0000 Subject: Bug 355283: Lock out a user account on a particular IP for 30 minutes if they fail to log in 5 times from that IP. Patch by Max Kanat-Alexander r=LpSolit, a=LpSolit --- Bugzilla/User.pm | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) (limited to 'Bugzilla/User.pm') diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index 3e6f3b6ba..e8ea2878e 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -65,6 +65,11 @@ use base qw(Bugzilla::Object Exporter); # Constants ##################################################################### +# Used as the IP for authentication failures for password-lockout purposes +# when there is no IP (for example, if we're doing authentication from the +# command line for some reason). +use constant NO_IP => '255.255.255.255'; + use constant USER_MATCH_MULTIPLE => -1; use constant USER_MATCH_FAILED => 0; use constant USER_MATCH_SUCCESS => 1; @@ -247,6 +252,15 @@ sub is_disabled { $_[0]->disabledtext ? 1 : 0; } sub showmybugslink { $_[0]->{showmybugslink}; } sub email_disabled { $_[0]->{disable_mail}; } sub email_enabled { !($_[0]->{disable_mail}); } +sub cryptpassword { + my $self = shift; + # We don't store it because we never want it in the object (we + # don't want to accidentally dump even the hash somewhere). + my ($pw) = Bugzilla->dbh->selectrow_array( + 'SELECT cryptpassword FROM profiles WHERE userid = ?', + undef, $self->id); + return $pw; +} sub set_authorizer { my ($self, $authorizer) = @_; @@ -1655,6 +1669,54 @@ sub create { return $user; } +########################### +# Account Lockout Methods # +########################### + +sub account_is_locked_out { + my $self = shift; + my $login_failures = scalar @{ $self->account_ip_login_failures }; + return $login_failures >= MAX_LOGIN_ATTEMPTS ? 1 : 0; +} + +sub note_login_failure { + my $self = shift; + my $ip_addr = Bugzilla->cgi->remote_addr || NO_IP; + trick_taint($ip_addr); + Bugzilla->dbh->do("INSERT INTO login_failure (user_id, ip_addr, login_time) + VALUES (?, ?, LOCALTIMESTAMP(0))", + undef, $self->id, $ip_addr); + delete $self->{account_ip_login_failures}; +} + +sub clear_login_failures { + my $self = shift; + my $ip_addr = Bugzilla->cgi->remote_addr || NO_IP; + trick_taint($ip_addr); + Bugzilla->dbh->do( + 'DELETE FROM login_failure WHERE user_id = ? AND ip_addr = ?', + undef, $self->id, $ip_addr); + delete $self->{account_ip_login_failures}; +} + +sub account_ip_login_failures { + my $self = shift; + my $dbh = Bugzilla->dbh; + my $time = $dbh->sql_interval(LOGIN_LOCKOUT_INTERVAL, 'MINUTE'); + my $ip_addr = Bugzilla->cgi->remote_addr || NO_IP; + trick_taint($ip_addr); + $self->{account_ip_login_failures} ||= Bugzilla->dbh->selectall_arrayref( + "SELECT login_time, ip_addr, user_id FROM login_failure + WHERE user_id = ? AND login_time > LOCALTIMESTAMP(0) - $time + AND ip_addr = ? + ORDER BY login_time", {Slice => {}}, $self->id, $ip_addr); + return $self->{account_ip_login_failures}; +} + +############### +# Subroutines # +############### + sub is_available_username { my ($username, $old_username) = @_; @@ -1848,6 +1910,29 @@ groups. =back +=head2 Account Lockout + +=over + +=item C + +Returns C<1> if the account has failed to log in too many times recently, +and thus is locked out for a period of time. Returns C<0> otherwise. + +=item C + +Returns an arrayref of hashrefs, that contains information about the recent +times that this account has failed to log in from the current remote IP. +The hashes contain C, C, and C. + +=item C + +This notes that this account has failed to log in, and stores the fact +in the database. The storing happens immediately, it does not wait for +you to call C. + +=back + =head2 Other Methods =over -- cgit v1.2.3-24-g4f1b