From 8250145a0cbfd15aa16c2ad4e2235d1afe3a7359 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 11 Jan 2021 21:27:01 +0100 Subject: [PATCH] Refs #31274 -- Removed support for the pre-Django 3.1 encoding format of sessions. Per deprecation timeline. --- django/contrib/sessions/backends/base.py | 57 +++--------------------- docs/releases/4.0.txt | 2 + tests/sessions_tests/tests.py | 26 +++-------- 3 files changed, 15 insertions(+), 70 deletions(-) diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 143c08fbef4..62ee1f0d894 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -1,16 +1,11 @@ -import base64 import logging import string from datetime import datetime, timedelta from django.conf import settings -from django.contrib.sessions.exceptions import SuspiciousSession from django.core import signing -from django.core.exceptions import SuspiciousOperation from django.utils import timezone -from django.utils.crypto import ( - constant_time_compare, get_random_string, salted_hmac, -) +from django.utils.crypto import get_random_string from django.utils.module_loading import import_string # session_key should not be case sensitive because some backends can store it @@ -91,16 +86,8 @@ class SessionBase: def delete_test_cookie(self): del self[self.TEST_COOKIE_NAME] - def _hash(self, value): - # RemovedInDjango40Warning: pre-Django 3.1 format will be invalid. - key_salt = "django.contrib.sessions" + self.__class__.__name__ - return salted_hmac(key_salt, value).hexdigest() - def encode(self, session_dict): "Return the given session dictionary serialized and encoded as a string." - # RemovedInDjango40Warning: DEFAULT_HASHING_ALGORITHM will be removed. - if settings.DEFAULT_HASHING_ALGORITHM == 'sha1': - return self._legacy_encode(session_dict) return signing.dumps( session_dict, salt=self.key_salt, serializer=self.serializer, compress=True, @@ -109,44 +96,14 @@ class SessionBase: def decode(self, session_data): try: return signing.loads(session_data, salt=self.key_salt, serializer=self.serializer) - # RemovedInDjango40Warning: when the deprecation ends, handle here - # exceptions similar to what _legacy_decode() does now. except signing.BadSignature: - try: - # Return an empty session if data is not in the pre-Django 3.1 - # format. - return self._legacy_decode(session_data) - except Exception: - logger = logging.getLogger('django.security.SuspiciousSession') - logger.warning('Session data corrupted') - return {} + logger = logging.getLogger('django.security.SuspiciousSession') + logger.warning('Session data corrupted') except Exception: - return self._legacy_decode(session_data) - - def _legacy_encode(self, session_dict): - # RemovedInDjango40Warning. - serialized = self.serializer().dumps(session_dict) - hash = self._hash(serialized) - return base64.b64encode(hash.encode() + b':' + serialized).decode('ascii') - - def _legacy_decode(self, session_data): - # RemovedInDjango40Warning: pre-Django 3.1 format will be invalid. - encoded_data = base64.b64decode(session_data.encode('ascii')) - try: - # could produce ValueError if there is no ':' - hash, serialized = encoded_data.split(b':', 1) - expected_hash = self._hash(serialized) - if not constant_time_compare(hash.decode(), expected_hash): - raise SuspiciousSession("Session data corrupted") - else: - return self.serializer().loads(serialized) - except Exception as e: - # ValueError, SuspiciousOperation, unpickling exceptions. If any of - # these happen, just return an empty dictionary (an empty session). - if isinstance(e, SuspiciousOperation): - logger = logging.getLogger('django.security.%s' % e.__class__.__name__) - logger.warning(str(e)) - return {} + # ValueError, unpickling exceptions. If any of these happen, just + # return an empty dictionary (an empty session). + pass + return {} def update(self, dict_): self._session.update(dict_) diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index a2f04227fd9..a6f5bdb12ec 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -283,3 +283,5 @@ to remove usage of these features. * Support for the pre-Django 3.1 password reset tokens in the admin site (that use the SHA-1 hashing algorithm) is removed. + +* Support for the pre-Django 3.1 encoding format of sessions is removed. diff --git a/tests/sessions_tests/tests.py b/tests/sessions_tests/tests.py index 73d2a13a9f3..c2743ef48b2 100644 --- a/tests/sessions_tests/tests.py +++ b/tests/sessions_tests/tests.py @@ -31,13 +31,13 @@ from django.core import management from django.core.cache import caches from django.core.cache.backends.base import InvalidCacheBackendError from django.core.exceptions import ImproperlyConfigured +from django.core.signing import TimestampSigner from django.http import HttpResponse from django.test import ( RequestFactory, SimpleTestCase, TestCase, ignore_warnings, override_settings, ) from django.utils import timezone -from django.utils.deprecation import RemovedInDjango40Warning from .models import SessionStore as CustomDatabaseSession @@ -315,25 +315,6 @@ class SessionTestsMixin: encoded = self.session.encode(data) self.assertEqual(self.session.decode(encoded), data) - @override_settings(SECRET_KEY='django_tests_secret_key') - def test_decode_legacy(self): - # RemovedInDjango40Warning: pre-Django 3.1 sessions will be invalid. - legacy_encoded = ( - 'OWUzNTNmNWQxNTBjOWExZmM4MmQ3NzNhMDRmMjU4NmYwNDUyNGI2NDp7ImEgdGVzd' - 'CBrZXkiOiJhIHRlc3QgdmFsdWUifQ==' - ) - self.assertEqual( - self.session.decode(legacy_encoded), - {'a test key': 'a test value'}, - ) - - @ignore_warnings(category=RemovedInDjango40Warning) - def test_default_hashing_algorith_legacy_decode(self): - with self.settings(DEFAULT_HASHING_ALGORITHM='sha1'): - data = {'a test key': 'a test value'} - encoded = self.session.encode(data) - self.assertEqual(self.session._legacy_decode(encoded), data) - def test_decode_failure_logged_to_security(self): tests = [ base64.b64encode(b'flaskdj:alkdjf').decode('ascii'), @@ -346,6 +327,11 @@ class SessionTestsMixin: # The failed decode is logged. self.assertIn('Session data corrupted', cm.output[0]) + def test_decode_serializer_exception(self): + signer = TimestampSigner(salt=self.session.key_salt) + encoded = signer.sign(b'invalid data') + self.assertEqual(self.session.decode(encoded), {}) + def test_actual_expiry(self): # this doesn't work with JSONSerializer (serializing timedelta) with override_settings(SESSION_SERIALIZER='django.contrib.sessions.serializers.PickleSerializer'):