Commit 5dbca13f authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

Fixed #20760 -- Reduced timing variation in ModelBackend.

Thanks jpaglier and erikr.
parent e716518a
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -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):
        """
+24 −1
Original line number Diff line number Diff line
@@ -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):