summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/contrib/auth/backends.py4
-rw-r--r--django/contrib/auth/tests/test_auth_backends.py25
2 files changed, 27 insertions, 2 deletions
diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py
index 6b31f72b03..cb79291c17 100644
--- a/django/contrib/auth/backends.py
+++ b/django/contrib/auth/backends.py
@@ -17,7 +17,9 @@ class ModelBackend(object):
if user.check_password(password):
return user
except UserModel.DoesNotExist:
- return None
+ # Run the default password hasher once to reduce the timing
+ # difference between an existing and a non-existing user (#20760).
+ UserModel().set_password(password)
def get_group_permissions(self, user_obj, obj=None):
"""
diff --git a/django/contrib/auth/tests/test_auth_backends.py b/django/contrib/auth/tests/test_auth_backends.py
index fc5a80e8dd..b48df91cfb 100644
--- a/django/contrib/auth/tests/test_auth_backends.py
+++ b/django/contrib/auth/tests/test_auth_backends.py
@@ -12,6 +12,17 @@ from django.contrib.auth import authenticate, get_user
from django.http import HttpRequest
from django.test import TestCase
from django.test.utils import override_settings
+from django.contrib.auth.hashers import MD5PasswordHasher
+
+
+class CountingMD5PasswordHasher(MD5PasswordHasher):
+ """Hasher that counts how many times it computes a hash."""
+
+ calls = 0
+
+ def encode(self, *args, **kwargs):
+ type(self).calls += 1
+ return super(CountingMD5PasswordHasher, self).encode(*args, **kwargs)
class BaseModelBackendTest(object):
@@ -107,10 +118,22 @@ class BaseModelBackendTest(object):
self.assertEqual(user.get_all_permissions(), set(['auth.test']))
def test_get_all_superuser_permissions(self):
- "A superuser has all permissions. Refs #14795"
+ """A superuser has all permissions. Refs #14795."""
user = self.UserModel._default_manager.get(pk=self.superuser.pk)
self.assertEqual(len(user.get_all_permissions()), len(Permission.objects.all()))
+ @override_settings(PASSWORD_HASHERS=('django.contrib.auth.tests.test_auth_backends.CountingMD5PasswordHasher',))
+ def test_authentication_timing(self):
+ """Hasher is run once regardless of whether the user exists. Refs #20760."""
+ CountingMD5PasswordHasher.calls = 0
+ username = getattr(self.user, self.UserModel.USERNAME_FIELD)
+ authenticate(username=username, password='test')
+ self.assertEqual(CountingMD5PasswordHasher.calls, 1)
+
+ CountingMD5PasswordHasher.calls = 0
+ authenticate(username='no_such_user', password='test')
+ self.assertEqual(CountingMD5PasswordHasher.calls, 1)
+
@skipIfCustomUser
class ModelBackendTest(BaseModelBackendTest, TestCase):