Fixed #31842 -- Added DEFAULT_HASHING_ALGORITHM transitional setting.
It's a transitional setting helpful in migrating multiple instance of the same project to Django 3.1+. Thanks Markus Holtermann for the report and review, Florian Apolloner for the implementation idea and review, and Carlton Gibson for the review.
This commit is contained in:
parent
bce4a53670
commit
d907371ef9
|
@ -27,6 +27,12 @@ PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG = (
|
||||||
'PASSWORD_RESET_TIMEOUT instead.'
|
'PASSWORD_RESET_TIMEOUT instead.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DEFAULT_HASHING_ALGORITHM_DEPRECATED_MSG = (
|
||||||
|
'The DEFAULT_HASHING_ALGORITHM transitional setting is deprecated. '
|
||||||
|
'Support for it and tokens, cookies, sessions, and signatures that use '
|
||||||
|
'SHA-1 hashing algorithm will be removed in Django 4.0.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SettingsReference(str):
|
class SettingsReference(str):
|
||||||
"""
|
"""
|
||||||
|
@ -195,6 +201,9 @@ class Settings:
|
||||||
setattr(self, 'PASSWORD_RESET_TIMEOUT', self.PASSWORD_RESET_TIMEOUT_DAYS * 60 * 60 * 24)
|
setattr(self, 'PASSWORD_RESET_TIMEOUT', self.PASSWORD_RESET_TIMEOUT_DAYS * 60 * 60 * 24)
|
||||||
warnings.warn(PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG, RemovedInDjango40Warning)
|
warnings.warn(PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG, RemovedInDjango40Warning)
|
||||||
|
|
||||||
|
if self.is_overridden('DEFAULT_HASHING_ALGORITHM'):
|
||||||
|
warnings.warn(DEFAULT_HASHING_ALGORITHM_DEPRECATED_MSG, RemovedInDjango40Warning)
|
||||||
|
|
||||||
if hasattr(time, 'tzset') and self.TIME_ZONE:
|
if hasattr(time, 'tzset') and self.TIME_ZONE:
|
||||||
# When we can, attempt to validate the timezone. If we can't find
|
# When we can, attempt to validate the timezone. If we can't find
|
||||||
# this file, no check happens and it's harmless.
|
# this file, no check happens and it's harmless.
|
||||||
|
@ -241,6 +250,8 @@ class UserSettingsHolder:
|
||||||
if name == 'PASSWORD_RESET_TIMEOUT_DAYS':
|
if name == 'PASSWORD_RESET_TIMEOUT_DAYS':
|
||||||
setattr(self, 'PASSWORD_RESET_TIMEOUT', value * 60 * 60 * 24)
|
setattr(self, 'PASSWORD_RESET_TIMEOUT', value * 60 * 60 * 24)
|
||||||
warnings.warn(PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG, RemovedInDjango40Warning)
|
warnings.warn(PASSWORD_RESET_TIMEOUT_DAYS_DEPRECATED_MSG, RemovedInDjango40Warning)
|
||||||
|
if name == 'DEFAULT_HASHING_ALGORITHM':
|
||||||
|
warnings.warn(DEFAULT_HASHING_ALGORITHM_DEPRECATED_MSG, RemovedInDjango40Warning)
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
def __delattr__(self, name):
|
def __delattr__(self, name):
|
||||||
|
|
|
@ -436,6 +436,12 @@ WSGI_APPLICATION = None
|
||||||
# you may be opening yourself up to a security risk.
|
# you may be opening yourself up to a security risk.
|
||||||
SECURE_PROXY_SSL_HEADER = None
|
SECURE_PROXY_SSL_HEADER = None
|
||||||
|
|
||||||
|
# Default hashing algorithm to use for encoding cookies, password reset tokens
|
||||||
|
# in the admin site, user sessions, and signatures. It's a transitional setting
|
||||||
|
# helpful in migrating multiple instance of the same project to Django 3.1+.
|
||||||
|
# Algorithm must be 'sha1' or 'sha256'.
|
||||||
|
DEFAULT_HASHING_ALGORITHM = 'sha256'
|
||||||
|
|
||||||
##############
|
##############
|
||||||
# MIDDLEWARE #
|
# MIDDLEWARE #
|
||||||
##############
|
##############
|
||||||
|
|
|
@ -4,6 +4,7 @@ not in INSTALLED_APPS.
|
||||||
"""
|
"""
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth import password_validation
|
from django.contrib.auth import password_validation
|
||||||
from django.contrib.auth.hashers import (
|
from django.contrib.auth.hashers import (
|
||||||
check_password, is_password_usable, make_password,
|
check_password, is_password_usable, make_password,
|
||||||
|
@ -130,7 +131,14 @@ class AbstractBaseUser(models.Model):
|
||||||
Return an HMAC of the password field.
|
Return an HMAC of the password field.
|
||||||
"""
|
"""
|
||||||
key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
|
key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
|
||||||
return salted_hmac(key_salt, self.password, algorithm='sha256').hexdigest()
|
return salted_hmac(
|
||||||
|
key_salt,
|
||||||
|
self.password,
|
||||||
|
# RemovedInDjango40Warning: when the deprecation ends, replace
|
||||||
|
# with:
|
||||||
|
# algorithm='sha256',
|
||||||
|
algorithm=settings.DEFAULT_HASHING_ALGORITHM,
|
||||||
|
).hexdigest()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_email_field_name(cls):
|
def get_email_field_name(cls):
|
||||||
|
|
|
@ -11,11 +11,14 @@ class PasswordResetTokenGenerator:
|
||||||
reset mechanism.
|
reset mechanism.
|
||||||
"""
|
"""
|
||||||
key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
|
key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
|
||||||
algorithm = 'sha256'
|
algorithm = None
|
||||||
secret = None
|
secret = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.secret = self.secret or settings.SECRET_KEY
|
self.secret = self.secret or settings.SECRET_KEY
|
||||||
|
# RemovedInDjango40Warning: when the deprecation ends, replace with:
|
||||||
|
# self.algorithm = self.algorithm or 'sha256'
|
||||||
|
self.algorithm = self.algorithm or settings.DEFAULT_HASHING_ALGORITHM
|
||||||
|
|
||||||
def make_token(self, user):
|
def make_token(self, user):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -116,6 +116,11 @@ E023 = Error(
|
||||||
id='security.E023',
|
id='security.E023',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
E100 = Error(
|
||||||
|
"DEFAULT_HASHING_ALGORITHM must be 'sha1' or 'sha256'.",
|
||||||
|
id='security.E100',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _security_middleware():
|
def _security_middleware():
|
||||||
return 'django.middleware.security.SecurityMiddleware' in settings.MIDDLEWARE
|
return 'django.middleware.security.SecurityMiddleware' in settings.MIDDLEWARE
|
||||||
|
@ -228,3 +233,11 @@ def check_referrer_policy(app_configs, **kwargs):
|
||||||
if not values <= REFERRER_POLICY_VALUES:
|
if not values <= REFERRER_POLICY_VALUES:
|
||||||
return [E023]
|
return [E023]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
# RemovedInDjango40Warning
|
||||||
|
@register(Tags.security)
|
||||||
|
def check_default_hashing_algorithm(app_configs, **kwargs):
|
||||||
|
if settings.DEFAULT_HASHING_ALGORITHM not in {'sha1', 'sha256'}:
|
||||||
|
return [E100]
|
||||||
|
return []
|
||||||
|
|
|
@ -147,7 +147,7 @@ class Signer:
|
||||||
# RemovedInDjango40Warning.
|
# RemovedInDjango40Warning.
|
||||||
legacy_algorithm = 'sha1'
|
legacy_algorithm = 'sha1'
|
||||||
|
|
||||||
def __init__(self, key=None, sep=':', salt=None, algorithm='sha256'):
|
def __init__(self, key=None, sep=':', salt=None, algorithm=None):
|
||||||
self.key = key or settings.SECRET_KEY
|
self.key = key or settings.SECRET_KEY
|
||||||
self.sep = sep
|
self.sep = sep
|
||||||
if _SEP_UNSAFE.match(self.sep):
|
if _SEP_UNSAFE.match(self.sep):
|
||||||
|
@ -156,7 +156,9 @@ class Signer:
|
||||||
'only A-z0-9-_=)' % sep,
|
'only A-z0-9-_=)' % sep,
|
||||||
)
|
)
|
||||||
self.salt = salt or '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
|
self.salt = salt or '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
|
||||||
self.algorithm = algorithm
|
# RemovedInDjango40Warning: when the deprecation ends, replace with:
|
||||||
|
# self.algorithm = algorithm or 'sha256'
|
||||||
|
self.algorithm = algorithm or settings.DEFAULT_HASHING_ALGORITHM
|
||||||
|
|
||||||
def signature(self, value):
|
def signature(self, value):
|
||||||
return base64_hmac(self.salt + 'signer', value, self.key, algorithm=self.algorithm)
|
return base64_hmac(self.salt + 'signer', value, self.key, algorithm=self.algorithm)
|
||||||
|
|
|
@ -118,6 +118,8 @@ details on these changes.
|
||||||
|
|
||||||
* The ``{% ifequal %}`` and ``{% ifnotequal %}`` template tags will be removed.
|
* The ``{% ifequal %}`` and ``{% ifnotequal %}`` template tags will be removed.
|
||||||
|
|
||||||
|
* The ``DEFAULT_HASHING_ALGORITHM`` transitional setting will be removed.
|
||||||
|
|
||||||
.. _deprecation-removed-in-3.1:
|
.. _deprecation-removed-in-3.1:
|
||||||
|
|
||||||
3.1
|
3.1
|
||||||
|
|
|
@ -484,6 +484,12 @@ The following checks are run if you use the :option:`check --deploy` option:
|
||||||
* **security.E023**: You have set the :setting:`SECURE_REFERRER_POLICY` setting
|
* **security.E023**: You have set the :setting:`SECURE_REFERRER_POLICY` setting
|
||||||
to an invalid value.
|
to an invalid value.
|
||||||
|
|
||||||
|
The following checks verify that your security-related settings are correctly
|
||||||
|
configured:
|
||||||
|
|
||||||
|
* **security.E100**: :setting:`DEFAULT_HASHING_ALGORITHM` must be ``'sha1'`` or
|
||||||
|
``'sha256'``.
|
||||||
|
|
||||||
Signals
|
Signals
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -1295,6 +1295,27 @@ Default email address to use for various automated correspondence from the
|
||||||
site manager(s). This doesn't include error messages sent to :setting:`ADMINS`
|
site manager(s). This doesn't include error messages sent to :setting:`ADMINS`
|
||||||
and :setting:`MANAGERS`; for that, see :setting:`SERVER_EMAIL`.
|
and :setting:`MANAGERS`; for that, see :setting:`SERVER_EMAIL`.
|
||||||
|
|
||||||
|
.. setting:: DEFAULT_HASHING_ALGORITHM
|
||||||
|
|
||||||
|
``DEFAULT_HASHING_ALGORITHM``
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 3.1
|
||||||
|
|
||||||
|
Default: ``'sha256'``
|
||||||
|
|
||||||
|
Default hashing algorithm to use for encoding cookies, password reset tokens in
|
||||||
|
the admin site, user sessions, and signatures created by
|
||||||
|
:class:`django.core.signing.Signer` and :meth:`django.core.signing.dumps`.
|
||||||
|
Algorithm must be ``'sha1'`` or ``'sha256'``. See
|
||||||
|
:ref:`release notes <default-hashing-algorithm-usage>` for usage details.
|
||||||
|
|
||||||
|
.. deprecated:: 3.1
|
||||||
|
|
||||||
|
This transitional setting is deprecated. Support for it and tokens,
|
||||||
|
cookies, sessions, and signatures that use SHA-1 hashing algorithm will be
|
||||||
|
removed in Django 4.0.
|
||||||
|
|
||||||
.. setting:: DEFAULT_INDEX_TABLESPACE
|
.. setting:: DEFAULT_INDEX_TABLESPACE
|
||||||
|
|
||||||
``DEFAULT_INDEX_TABLESPACE``
|
``DEFAULT_INDEX_TABLESPACE``
|
||||||
|
|
|
@ -96,6 +96,27 @@ and generate and apply a database migration. For now, the old fields and
|
||||||
transforms are left as a reference to the new ones and are :ref:`deprecated as
|
transforms are left as a reference to the new ones and are :ref:`deprecated as
|
||||||
of this release <deprecated-jsonfield>`.
|
of this release <deprecated-jsonfield>`.
|
||||||
|
|
||||||
|
.. _default-hashing-algorithm-usage:
|
||||||
|
|
||||||
|
``DEFAULT_HASHING_ALGORITHM`` settings
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
The new :setting:`DEFAULT_HASHING_ALGORITHM` transitional setting allows
|
||||||
|
specifying the default hashing algorithm to use for encoding cookies, password
|
||||||
|
reset tokens in the admin site, user sessions, and signatures created by
|
||||||
|
:class:`django.core.signing.Signer` and :meth:`django.core.signing.dumps`.
|
||||||
|
|
||||||
|
Support for SHA-256 was added in Django 3.1. If you are upgrading multiple
|
||||||
|
instances of the same project to Django 3.1, you should set
|
||||||
|
:setting:`DEFAULT_HASHING_ALGORITHM` to ``'sha1'`` during the transition, in
|
||||||
|
order to allow compatibility with the older versions of Django. Once the
|
||||||
|
transition to 3.1 is complete you can stop overriding
|
||||||
|
:setting:`DEFAULT_HASHING_ALGORITHM`.
|
||||||
|
|
||||||
|
This setting is deprecated as of this release, because support for tokens,
|
||||||
|
cookies, sessions, and signatures that use SHA-1 algorithm will be removed in
|
||||||
|
Django 4.0.
|
||||||
|
|
||||||
Minor features
|
Minor features
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
@ -794,6 +815,8 @@ Miscellaneous
|
||||||
<django.template.backends.django.DjangoTemplates>` option in
|
<django.template.backends.django.DjangoTemplates>` option in
|
||||||
:setting:`OPTIONS <TEMPLATES-OPTIONS>`.
|
:setting:`OPTIONS <TEMPLATES-OPTIONS>`.
|
||||||
|
|
||||||
|
* ``DEFAULT_HASHING_ALGORITHM`` transitional setting is deprecated.
|
||||||
|
|
||||||
.. _removed-features-3.1:
|
.. _removed-features-3.1:
|
||||||
|
|
||||||
Features removed in 3.1
|
Features removed in 3.1
|
||||||
|
|
|
@ -81,13 +81,13 @@ generate signatures. You can use a different secret by passing it to the
|
||||||
>>> value
|
>>> value
|
||||||
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
|
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
|
||||||
|
|
||||||
.. class:: Signer(key=None, sep=':', salt=None, algorithm='sha256')
|
.. class:: Signer(key=None, sep=':', salt=None, algorithm=None)
|
||||||
|
|
||||||
Returns a signer which uses ``key`` to generate signatures and ``sep`` to
|
Returns a signer which uses ``key`` to generate signatures and ``sep`` to
|
||||||
separate values. ``sep`` cannot be in the :rfc:`URL safe base64 alphabet
|
separate values. ``sep`` cannot be in the :rfc:`URL safe base64 alphabet
|
||||||
<4648#section-5>`. This alphabet contains alphanumeric characters, hyphens,
|
<4648#section-5>`. This alphabet contains alphanumeric characters, hyphens,
|
||||||
and underscores. ``algorithm`` must be an algorithm supported by
|
and underscores. ``algorithm`` must be an algorithm supported by
|
||||||
:py:mod:`hashlib`.
|
:py:mod:`hashlib`, it defaults to ``'sha256'``.
|
||||||
|
|
||||||
.. versionchanged:: 3.1
|
.. versionchanged:: 3.1
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,9 @@ from django.contrib.auth import HASH_SESSION_KEY
|
||||||
from django.contrib.auth.middleware import AuthenticationMiddleware
|
from django.contrib.auth.middleware import AuthenticationMiddleware
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.test import TestCase
|
from django.test import TestCase, override_settings
|
||||||
|
from django.test.utils import ignore_warnings
|
||||||
|
from django.utils.deprecation import RemovedInDjango40Warning
|
||||||
|
|
||||||
|
|
||||||
class TestAuthenticationMiddleware(TestCase):
|
class TestAuthenticationMiddleware(TestCase):
|
||||||
|
@ -32,6 +34,12 @@ class TestAuthenticationMiddleware(TestCase):
|
||||||
self.assertIsNotNone(self.request.user)
|
self.assertIsNotNone(self.request.user)
|
||||||
self.assertFalse(self.request.user.is_anonymous)
|
self.assertFalse(self.request.user.is_anonymous)
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango40Warning)
|
||||||
|
def test_session_default_hashing_algorithm(self):
|
||||||
|
hash_session = self.client.session[HASH_SESSION_KEY]
|
||||||
|
with override_settings(DEFAULT_HASHING_ALGORITHM='sha1'):
|
||||||
|
self.assertNotEqual(hash_session, self.user.get_session_auth_hash())
|
||||||
|
|
||||||
def test_changed_password_invalidates_session(self):
|
def test_changed_password_invalidates_session(self):
|
||||||
# After password change, user should be anonymous
|
# After password change, user should be anonymous
|
||||||
self.user.set_password('new_password')
|
self.user.set_password('new_password')
|
||||||
|
|
|
@ -23,6 +23,7 @@ class DeprecationTests(TestCase):
|
||||||
class Mocked(PasswordResetTokenGenerator):
|
class Mocked(PasswordResetTokenGenerator):
|
||||||
def __init__(self, now):
|
def __init__(self, now):
|
||||||
self._now_val = now
|
self._now_val = now
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
def _now(self):
|
def _now(self):
|
||||||
return self._now_val
|
return self._now_val
|
||||||
|
|
|
@ -4,11 +4,14 @@ from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.test.utils import ignore_warnings
|
||||||
|
from django.utils.deprecation import RemovedInDjango40Warning
|
||||||
|
|
||||||
|
|
||||||
class MockedPasswordResetTokenGenerator(PasswordResetTokenGenerator):
|
class MockedPasswordResetTokenGenerator(PasswordResetTokenGenerator):
|
||||||
def __init__(self, now):
|
def __init__(self, now):
|
||||||
self._now_val = now
|
self._now_val = now
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
def _now(self):
|
def _now(self):
|
||||||
return self._now_val
|
return self._now_val
|
||||||
|
@ -88,6 +91,15 @@ class TokenGeneratorTest(TestCase):
|
||||||
self.assertIs(p0.check_token(user, tk1), False)
|
self.assertIs(p0.check_token(user, tk1), False)
|
||||||
self.assertIs(p1.check_token(user, tk0), False)
|
self.assertIs(p1.check_token(user, tk0), False)
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango40Warning)
|
||||||
|
def test_token_default_hashing_algorithm(self):
|
||||||
|
user = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw')
|
||||||
|
with self.settings(DEFAULT_HASHING_ALGORITHM='sha1'):
|
||||||
|
generator = PasswordResetTokenGenerator()
|
||||||
|
self.assertEqual(generator.algorithm, 'sha1')
|
||||||
|
token = generator.make_token(user)
|
||||||
|
self.assertIs(generator.check_token(user, token), True)
|
||||||
|
|
||||||
def test_legacy_token_validation(self):
|
def test_legacy_token_validation(self):
|
||||||
# RemovedInDjango40Warning: pre-Django 3.1 tokens will be invalid.
|
# RemovedInDjango40Warning: pre-Django 3.1 tokens will be invalid.
|
||||||
user = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw')
|
user = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw')
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
import sys
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
|
from django.conf import (
|
||||||
|
DEFAULT_HASHING_ALGORITHM_DEPRECATED_MSG, Settings, settings,
|
||||||
|
)
|
||||||
|
from django.core.checks.security import base as security_base
|
||||||
|
from django.test import TestCase, ignore_warnings
|
||||||
|
from django.utils.deprecation import RemovedInDjango40Warning
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultHashingAlgorithmDeprecationTests(TestCase):
|
||||||
|
msg = DEFAULT_HASHING_ALGORITHM_DEPRECATED_MSG
|
||||||
|
|
||||||
|
def test_override_settings_warning(self):
|
||||||
|
with self.assertRaisesMessage(RemovedInDjango40Warning, self.msg):
|
||||||
|
with self.settings(DEFAULT_HASHING_ALGORITHM='sha1'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_settings_init_warning(self):
|
||||||
|
settings_module = ModuleType('fake_settings_module')
|
||||||
|
settings_module.SECRET_KEY = 'foo'
|
||||||
|
settings_module.DEFAULT_HASHING_ALGORITHM = 'sha1'
|
||||||
|
sys.modules['fake_settings_module'] = settings_module
|
||||||
|
try:
|
||||||
|
with self.assertRaisesMessage(RemovedInDjango40Warning, self.msg):
|
||||||
|
Settings('fake_settings_module')
|
||||||
|
finally:
|
||||||
|
del sys.modules['fake_settings_module']
|
||||||
|
|
||||||
|
def test_access(self):
|
||||||
|
# Warning is not raised on access.
|
||||||
|
self.assertEqual(settings.DEFAULT_HASHING_ALGORITHM, 'sha256')
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango40Warning)
|
||||||
|
def test_system_check_invalid_value(self):
|
||||||
|
tests = [
|
||||||
|
None,
|
||||||
|
256,
|
||||||
|
'invalid',
|
||||||
|
'md5',
|
||||||
|
'sha512',
|
||||||
|
]
|
||||||
|
for value in tests:
|
||||||
|
with self.subTest(value=value), self.settings(DEFAULT_HASHING_ALGORITHM=value):
|
||||||
|
self.assertEqual(
|
||||||
|
security_base.check_default_hashing_algorithm(None),
|
||||||
|
[security_base.E100],
|
||||||
|
)
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango40Warning)
|
||||||
|
def test_system_check_valid_value(self):
|
||||||
|
for value in ['sha1', 'sha256']:
|
||||||
|
with self.subTest(value=value), self.settings(DEFAULT_HASHING_ALGORITHM=value):
|
||||||
|
self.assertEqual(security_base.check_default_hashing_algorithm(None), [])
|
|
@ -7,6 +7,8 @@ from django.contrib.messages.storage.cookie import (
|
||||||
CookieStorage, MessageDecoder, MessageEncoder,
|
CookieStorage, MessageDecoder, MessageEncoder,
|
||||||
)
|
)
|
||||||
from django.test import SimpleTestCase, override_settings
|
from django.test import SimpleTestCase, override_settings
|
||||||
|
from django.test.utils import ignore_warnings
|
||||||
|
from django.utils.deprecation import RemovedInDjango40Warning
|
||||||
from django.utils.safestring import SafeData, mark_safe
|
from django.utils.safestring import SafeData, mark_safe
|
||||||
|
|
||||||
from .base import BaseTests
|
from .base import BaseTests
|
||||||
|
@ -169,3 +171,14 @@ class CookieTests(BaseTests, SimpleTestCase):
|
||||||
encoded_messages = '%s$%s' % (storage._legacy_hash(value), value)
|
encoded_messages = '%s$%s' % (storage._legacy_hash(value), value)
|
||||||
decoded_messages = storage._decode(encoded_messages)
|
decoded_messages = storage._decode(encoded_messages)
|
||||||
self.assertEqual(messages, decoded_messages)
|
self.assertEqual(messages, decoded_messages)
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango40Warning)
|
||||||
|
def test_default_hashing_algorithm(self):
|
||||||
|
messages = Message(constants.DEBUG, ['this', 'that'])
|
||||||
|
with self.settings(DEFAULT_HASHING_ALGORITHM='sha1'):
|
||||||
|
storage = self.get_storage()
|
||||||
|
encoded = storage._encode(messages)
|
||||||
|
decoded = storage._decode(encoded)
|
||||||
|
self.assertEqual(decoded, messages)
|
||||||
|
storage_default = self.get_storage()
|
||||||
|
self.assertNotEqual(encoded, storage_default._encode(messages))
|
||||||
|
|
|
@ -2,8 +2,9 @@ import datetime
|
||||||
|
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
from django.test.utils import freeze_time
|
from django.test.utils import freeze_time, ignore_warnings
|
||||||
from django.utils.crypto import InvalidAlgorithm
|
from django.utils.crypto import InvalidAlgorithm
|
||||||
|
from django.utils.deprecation import RemovedInDjango40Warning
|
||||||
|
|
||||||
|
|
||||||
class TestSigner(SimpleTestCase):
|
class TestSigner(SimpleTestCase):
|
||||||
|
@ -52,6 +53,14 @@ class TestSigner(SimpleTestCase):
|
||||||
'VzO9_jVu7R-VkqknHYNvw',
|
'VzO9_jVu7R-VkqknHYNvw',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango40Warning)
|
||||||
|
def test_default_hashing_algorithm(self):
|
||||||
|
signer = signing.Signer('predictable-secret', algorithm='sha1')
|
||||||
|
signature_sha1 = signer.signature('hello')
|
||||||
|
with self.settings(DEFAULT_HASHING_ALGORITHM='sha1'):
|
||||||
|
signer = signing.Signer('predictable-secret')
|
||||||
|
self.assertEqual(signer.signature('hello'), signature_sha1)
|
||||||
|
|
||||||
def test_invalid_algorithm(self):
|
def test_invalid_algorithm(self):
|
||||||
signer = signing.Signer('predictable-secret', algorithm='whatever')
|
signer = signing.Signer('predictable-secret', algorithm='whatever')
|
||||||
msg = "'whatever' is not an algorithm accepted by the hashlib module."
|
msg = "'whatever' is not an algorithm accepted by the hashlib module."
|
||||||
|
@ -134,6 +143,13 @@ class TestSigner(SimpleTestCase):
|
||||||
signed = 'ImEgc3RyaW5nIFx1MjAyMCI:1k1beT:ZfNhN1kdws7KosUleOvuYroPHEc'
|
signed = 'ImEgc3RyaW5nIFx1MjAyMCI:1k1beT:ZfNhN1kdws7KosUleOvuYroPHEc'
|
||||||
self.assertEqual(signing.loads(signed), value)
|
self.assertEqual(signing.loads(signed), value)
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango40Warning)
|
||||||
|
def test_dumps_loads_default_hashing_algorithm_sha1(self):
|
||||||
|
value = 'a string \u2020'
|
||||||
|
with self.settings(DEFAULT_HASHING_ALGORITHM='sha1'):
|
||||||
|
signed = signing.dumps(value)
|
||||||
|
self.assertEqual(signing.loads(signed), value)
|
||||||
|
|
||||||
def test_decode_detects_tampering(self):
|
def test_decode_detects_tampering(self):
|
||||||
"loads should raise exception for tampered objects"
|
"loads should raise exception for tampered objects"
|
||||||
transforms = (
|
transforms = (
|
||||||
|
|
Loading…
Reference in New Issue