Fix for LoginFailure.MultipleObjectsReturned in is_user_locked_out (replay)
The get_or_create function is vulnerable to race conditions in MySQL, which can cause the model LoginFailure to, in some cases, have more than one row for the same user, breaking the login for that user. Addinf functionality to expect and clean the error by deleting extra rows (by oldest lockout date), leaving just one entry and allowing the user to login. Replayed and squashed by @efischer19, initially commited by @laq
This commit is contained in:
@@ -771,6 +771,19 @@ class LoginFailures(models.Model):
|
||||
failure_count = models.IntegerField(default=0)
|
||||
lockout_until = models.DateTimeField(null=True)
|
||||
|
||||
@classmethod
|
||||
def _get_record_for_user(cls, user):
|
||||
"""
|
||||
Gets a user's record, and fixes any duplicates that may have arisen due to get_or_create
|
||||
race conditions. See https://code.djangoproject.com/ticket/13906 for details.
|
||||
|
||||
Use this method in place of `LoginFailures.objects.get(user=user)`
|
||||
"""
|
||||
records = LoginFailures.objects.filter(user=user).order_by('-lockout_until')
|
||||
for extra_record in records[1:]:
|
||||
extra_record.delete()
|
||||
return records.get()
|
||||
|
||||
@classmethod
|
||||
def is_feature_enabled(cls):
|
||||
"""
|
||||
@@ -784,7 +797,7 @@ class LoginFailures(models.Model):
|
||||
Static method to return in a given user has his/her account locked out
|
||||
"""
|
||||
try:
|
||||
record = LoginFailures.objects.get(user=user)
|
||||
record = cls._get_record_for_user(user)
|
||||
if not record.lockout_until:
|
||||
return False
|
||||
|
||||
@@ -819,7 +832,7 @@ class LoginFailures(models.Model):
|
||||
Removes the lockout counters (normally called after a successful login)
|
||||
"""
|
||||
try:
|
||||
entry = LoginFailures.objects.get(user=user)
|
||||
entry = cls._get_record_for_user(user)
|
||||
entry.delete()
|
||||
except ObjectDoesNotExist:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user