Ticket #20760: 20760_fix_hash_once.diff

File 20760_fix_hash_once.diff, 3.1 KB (added by jpaglier, 2 years ago)

set_password() solution and test

  • django/contrib/auth/backends.py

    diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py
    index 6b31f72..4ee326b 100644
    a b class ModelBackend(object): 
    1010
    1111    def authenticate(self, username=None, password=None, **kwargs):
    1212        UserModel = get_user_model()
     13
     14        user = UserModel()
     15
    1316        if username is None:
    1417            username = kwargs.get(UserModel.USERNAME_FIELD)
    1518        try:
    class ModelBackend(object): 
    1720            if user.check_password(password):
    1821                return user
    1922        except UserModel.DoesNotExist:
     23            #This call to set password is used to force the hasher to run.
     24            #It helps to even out the timing between a successful and
     25            #unsuccessful authentication attempt, helping to mitigate a timing
     26            #vuln.
     27            user.set_password(password)
    2028            return None
    2129
    2230    def get_group_permissions(self, user_obj, obj=None):
  • django/contrib/auth/tests/test_auth_backends.py

    diff --git a/django/contrib/auth/tests/test_auth_backends.py b/django/contrib/auth/tests/test_auth_backends.py
    index fc5a80e..aa69220 100644
    a b from django.contrib.auth import authenticate, get_user 
    1212from django.http import HttpRequest
    1313from django.test import TestCase
    1414from django.test.utils import override_settings
     15from django.contrib.auth.hashers import MD5PasswordHasher
    1516
     17"""
     18Mock hasher which counts number of times hashes are computed
     19
     20Used in test_authentication_timing
     21
     22Refs #20760
     23"""
     24class MockHasher(MD5PasswordHasher):
     25    def __init__(self):
     26        MockHasher.hash_count = 0
     27
     28    def encode(self, password, salt, iterations=None):
     29        MockHasher.hash_count = MockHasher.hash_count + 1
     30        return super(MockHasher, self).encode(password, salt, iterations)
    1631
    1732class BaseModelBackendTest(object):
    1833    """
    class BaseModelBackendTest(object): 
    111126        user = self.UserModel._default_manager.get(pk=self.superuser.pk)
    112127        self.assertEqual(len(user.get_all_permissions()), len(Permission.objects.all()))
    113128
     129    @override_settings(PASSWORD_HASHERS=('django.contrib.auth.tests.test_auth_backends.MockHasher',))
     130    def test_authentication_timing(self):
     131        """
     132        Tests that the hasher is run the same number of times whether a user exists or not
     133
     134        Refs #20760
     135        """
     136        #ensures the proper natural key is passed in custom models
     137        kwargs = {self.UserModel.USERNAME_FIELD : self.UserModel._default_manager.get(pk=1).natural_key()[0]}
     138        authenticate(password='test', **kwargs)
     139        exists = MockHasher.hash_count
     140
     141        MockHasher.hash_count = 0
     142
     143        #don't use these creds in a test account
     144        kwargs = {self.UserModel.USERNAME_FIELD : 'paglierani'}
     145        authenticate(password='justin', **kwargs)
     146        dne = MockHasher.hash_count
     147        self.assertEqual(exists, dne) #counts should be the same
     148
    114149
    115150@skipIfCustomUser
    116151class ModelBackendTest(BaseModelBackendTest, TestCase):
Back to Top