From d4fff711d4c97356bd6ba1273d2a5e349326eb5f Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 15 Feb 2020 12:20:37 +0100 Subject: [PATCH] Fixed #31274 -- Used signing infrastructure in SessionBase.encode()/decode(). Thanks Mariusz Felisiak and Florian Apolloner for the reviews. --- django/contrib/sessions/backends/base.py | 22 +++++++++++++++++++--- docs/internals/deprecation.txt | 2 ++ docs/releases/3.1.txt | 4 ++++ tests/sessions_tests/tests.py | 12 ++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 453f533e90..b5453160a5 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -6,6 +6,7 @@ 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 ( @@ -71,6 +72,10 @@ class SessionBase: del self._session[key] self.modified = True + @property + def key_salt(self): + return 'django.contrib.sessions.' + self.__class__.__qualname__ + def get(self, key, default=None): return self._session.get(key, default) @@ -97,16 +102,27 @@ class SessionBase: 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." - serialized = self.serializer().dumps(session_dict) - hash = self._hash(serialized) - return base64.b64encode(hash.encode() + b":" + serialized).decode('ascii') + return signing.dumps( + session_dict, salt=self.key_salt, serializer=self.serializer, + compress=True, + ) 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 Exception: + return self._legacy_decode(session_data) + + 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 ':' diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 2d7a72ae2f..3774afa59e 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -52,6 +52,8 @@ details on these changes. * Support for the pre-Django 3.1 password reset tokens in the admin site (that use the SHA-1 hashing algorithm) will be removed. +* Support for the pre-Django 3.1 encoding format of sessions will be removed. + * The ``get_request`` argument for ``django.utils.deprecation.MiddlewareMixin.__init__()`` will be required and won't accept ``None``. diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index d4ab35a310..669f2ca01e 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -539,6 +539,10 @@ Miscellaneous from the format generated by older versions of Django. Support for the old format remains until Django 4.0. +* The encoding format of sessions is different from the format generated by + older versions of Django. Support for the old format remains until Django + 4.0. + .. _removed-features-3.1: Features removed in 3.1 diff --git a/tests/sessions_tests/tests.py b/tests/sessions_tests/tests.py index fa675fe63d..6c6d7dd3f2 100644 --- a/tests/sessions_tests/tests.py +++ b/tests/sessions_tests/tests.py @@ -311,6 +311,18 @@ 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'}, + ) + def test_decode_failure_logged_to_security(self): bad_encode = base64.b64encode(b'flaskdj:alkdjf').decode('ascii') with self.assertLogs('django.security.SuspiciousSession', 'WARNING') as cm: