From 4525eab0779a2946063288224dcebb61ba382976 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Tue, 23 Jul 2013 15:41:09 +0200 Subject: [PATCH] [1.6.x] Fixed #20760 -- Reduced timing variation in ModelBackend. Thanks jpaglier and erikr. Backport of 5dbca13f3baa2e1bafd77e84a80ad6d8a074712e from master. --- django/contrib/auth/backends.py | 4 ++- .../contrib/auth/tests/test_auth_backends.py | 25 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py index 6b31f72b03c..cb79291c17a 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 fc5a80e8dda..b48df91cfb2 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):