Ticket #14390: patch_ticket14390.diff

File patch_ticket14390.diff, 9.5 KB (added by lrekucki, 5 years ago)

Patch with docs and tests. Also can also view & comment this on github: http://github.com/lqc/django/commit/246059c9b1da777974ad9d804989a2fb912208f1

  • django/contrib/auth/models.py

    diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
    index 16a8b99..d74ca05 100644
    a b import datetime 
    22import urllib
    33
    44from django.contrib import auth
     5from django.contrib.auth.utils import (get_hexdigest, make_password,
     6                                    check_password, is_password_usable)
    57from django.core.exceptions import ImproperlyConfigured
    68from django.db import models
    79from django.db.models.manager import EmptyManager
    810from django.contrib.contenttypes.models import ContentType
    911from django.utils.encoding import smart_str
    10 from django.utils.hashcompat import md5_constructor, sha_constructor
    1112from django.utils.translation import ugettext_lazy as _
    1213
    1314
    14 UNUSABLE_PASSWORD = '!' # This will never be a valid hash
    15 
    16 def get_hexdigest(algorithm, salt, raw_password):
    17     """
    18     Returns a string of the hexdigest of the given plaintext password and salt
    19     using the given algorithm ('md5', 'sha1' or 'crypt').
    20     """
    21     raw_password, salt = smart_str(raw_password), smart_str(salt)
    22     if algorithm == 'crypt':
    23         try:
    24             import crypt
    25         except ImportError:
    26             raise ValueError('"crypt" password algorithm not supported in this environment')
    27         return crypt.crypt(raw_password, salt)
    28 
    29     if algorithm == 'md5':
    30         return md5_constructor(salt + raw_password).hexdigest()
    31     elif algorithm == 'sha1':
    32         return sha_constructor(salt + raw_password).hexdigest()
    33     raise ValueError("Got unknown password algorithm type in password.")
    34 
    35 def check_password(raw_password, enc_password):
    36     """
    37     Returns a boolean of whether the raw_password was correct. Handles
    38     encryption formats behind the scenes.
    39     """
    40     algo, salt, hsh = enc_password.split('$')
    41     return hsh == get_hexdigest(algo, salt, raw_password)
    42 
    4315class SiteProfileNotAvailable(Exception):
    4416    pass
    4517
    class User(models.Model): 
    234206        return full_name.strip()
    235207
    236208    def set_password(self, raw_password):
    237         if raw_password is None:
    238             self.set_unusable_password()
    239         else:
    240             import random
    241             algo = 'sha1'
    242             salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
    243             hsh = get_hexdigest(algo, salt, raw_password)
    244             self.password = '%s$%s$%s' % (algo, salt, hsh)
     209        self.password = make_password('sha1', raw_password)
    245210
    246211    def check_password(self, raw_password):
    247212        """
    class User(models.Model): 
    261226
    262227    def set_unusable_password(self):
    263228        # Sets a value that will never be a valid hash
    264         self.password = UNUSABLE_PASSWORD
     229        self.password = make_password('sha1', None)
    265230
    266231    def has_usable_password(self):
    267         if self.password is None \
    268             or self.password == UNUSABLE_PASSWORD:
    269             return False
    270         else:
    271             return True
     232        return is_password_usable(self.password)
    272233
    273234    def get_group_permissions(self, obj=None):
    274235        """
  • django/contrib/auth/tests/__init__.py

    diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py
    index 98061a1..75dd415 100644
    a b  
    11from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest, AnonymousUserBackendTest, NoAnonymousUserBackendTest
    2 from django.contrib.auth.tests.basic import BasicTestCase
     2from django.contrib.auth.tests.basic import BasicTestCase, PasswordUtilsTestCase
    33from django.contrib.auth.tests.decorators import LoginRequiredTestCase
    44from django.contrib.auth.tests.forms import UserCreationFormTest, AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, UserChangeFormTest, PasswordResetFormTest
    55from django.contrib.auth.tests.remote_user \
  • django/contrib/auth/tests/basic.py

    diff --git a/django/contrib/auth/tests/basic.py b/django/contrib/auth/tests/basic.py
    index 7493dc6..6fb8d1b 100644
    a b  
    11from django.test import TestCase
     2from django.utils.unittest import skipUnless
    23from django.contrib.auth.models import User, AnonymousUser
     4from django.contrib.auth import utils
    35from django.core.management import call_command
    46from StringIO import StringIO
    57
     8try:
     9    import crypt as crypt_module
     10except ImportError:
     11    crypt_module = None
     12
     13class PasswordUtilsTestCase(TestCase):
     14
     15    def _test_make_password(self, algo):
     16        password = utils.make_password(algo, "foobar")
     17        self.assertTrue(utils.is_password_usable(password))
     18        self.assertTrue(utils.check_password("foobar", password))
     19
     20    def test_make_unusable(self):
     21        "Check that you can create an unusable password."
     22        password = utils.make_password("any", None)
     23        self.assertFalse(utils.is_password_usable(password))
     24        self.assertFalse(utils.check_password("foobar", password))
     25
     26    def test_make_password_sha1(self):
     27        "Check creating passwords with SHA1 algorithm."
     28        self._test_make_password("sha1")
     29
     30    def test_make_password_md5(self):
     31        "Check creating passwords with MD5 algorithm."
     32        self._test_make_password("md5")
     33
     34    @skipUnless(crypt_module, "no crypt module to generate password.")
     35    def test_make_password_crypt(self):
     36        "Check creating passwords with CRYPT algorithm."
     37        self._test_make_password("crypt")
     38
     39
    640class BasicTestCase(TestCase):
    741    def test_user(self):
    842        "Check that users can be created and can set their password"
  • new file django/contrib/auth/utils.py

    diff --git a/django/contrib/auth/utils.py b/django/contrib/auth/utils.py
    new file mode 100644
    index 0000000..b122dd6
    - +  
     1from django.utils.encoding import smart_str
     2from django.utils.hashcompat import md5_constructor, sha_constructor
     3import random
     4
     5
     6UNUSABLE_PASSWORD = '!' # This will never be a valid hash
     7
     8
     9def get_hexdigest(algorithm, salt, raw_password):
     10    """
     11    Returns a string of the hexdigest of the given plaintext password and salt
     12    using the given algorithm ('md5', 'sha1' or 'crypt').
     13    """
     14    raw_password, salt = smart_str(raw_password), smart_str(salt)
     15    if algorithm == 'crypt':
     16        try:
     17            import crypt
     18        except ImportError:
     19            raise ValueError('"crypt" password algorithm not supported in this environment')
     20        return crypt.crypt(raw_password, salt)
     21    if algorithm == 'md5':
     22        return md5_constructor(salt + raw_password).hexdigest()
     23    elif algorithm == 'sha1':
     24        return sha_constructor(salt + raw_password).hexdigest()
     25    raise ValueError("Got unknown password algorithm type in password.")
     26
     27
     28def check_password(raw_password, enc_password):
     29    """
     30    Returns a boolean of whether the raw_password was correct. Handles
     31    encryption formats behind the scenes.
     32    """
     33    parts = enc_password.split('$')
     34    if len(parts) != 3:
     35        return False
     36    algo, salt, hsh = parts
     37    return hsh == get_hexdigest(algo, salt, raw_password)
     38
     39
     40def is_password_usable(enc_password):
     41    return enc_password is not None and enc_password != UNUSABLE_PASSWORD
     42
     43
     44def make_password(algo, raw_password):
     45    """
     46    Produce a new password string in this format: algorithm$salt$hash
     47    """
     48    if raw_password is None:
     49        return UNUSABLE_PASSWORD
     50    salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
     51    hsh = get_hexdigest(algo, salt, raw_password)
     52    return '%s$%s$%s' % (algo, salt, hsh)
  • docs/topics/auth.txt

    diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
    index e0856e8..0c3ad56 100644
    a b Django provides two functions in :mod:`django.contrib.auth`: 
    621621
    622622.. _backends documentation: #other-authentication-sources
    623623
    624 Manually checking a user's password
     624Manually managing user's password
    625625-----------------------------------
    626626
     627.. versionchanged:: 1.3
     628
     629Function :func:`django.contrib.auth.models.check_password` has been moved to the
     630:mod:`django.contrib.auth.utils` module. Importing it from the old location
     631will still work, but you should update your imports.
     632
     633.. versionadded:: 1.3
     634
     635The :mod:`django.contrib.auth.utils` module provides a set of functions to
     636create and validate hashed password. You can use them independently from
     637the ``User`` model.
     638
    627639.. function:: check_password()
    628640
    629641    If you'd like to manually authenticate a user by comparing a plain-text
    630642    password to the hashed password in the database, use the convenience
    631     function :func:`django.contrib.auth.models.check_password`. It takes two
     643    function :func:`django.contrib.auth.utils.check_password`. It takes two
    632644    arguments: the plain-text password to check, and the full value of a user's
    633645    ``password`` field in the database to check against, and returns ``True``
    634646    if they match, ``False`` otherwise.
    635647
     648.. function:: make_password()
     649
     650    Creates a hashed password in the format used by this application. It takes
     651    two arguments: hashing algorithm to use and the password in plain-text.
     652    Currently supported algorithms are: `sha1`, `md5` and `crypt` if you have
     653    the `crypt` library installed. If the second argument is ``None``, an
     654    unusable password is returned (a one that will be never accepted by
     655    :func:`django.contrib.auth.utils.check_password`).
     656
     657.. function:: is_password_usable()
     658
     659    Checks if the given string is a hashed password that has a chance
     660    of being verified against :func:`django.contrib.auth.utils.check_password`.
     661
     662
    636663How to log a user out
    637664---------------------
    638665
Back to Top