Browse Source

Fixed #31274 -- Used signing infrastructure in SessionBase.encode()/decode().

Thanks Mariusz Felisiak and Florian Apolloner for the reviews.
Claude Paroz 5 years ago
parent
commit
d4fff711d4

+ 19 - 3
django/contrib/sessions/backends/base.py

@@ -6,6 +6,7 @@ from datetime import datetime, timedelta
 
 
 from django.conf import settings
 from django.conf import settings
 from django.contrib.sessions.exceptions import SuspiciousSession
 from django.contrib.sessions.exceptions import SuspiciousSession
+from django.core import signing
 from django.core.exceptions import SuspiciousOperation
 from django.core.exceptions import SuspiciousOperation
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.crypto import (
 from django.utils.crypto import (
@@ -71,6 +72,10 @@ class SessionBase:
         del self._session[key]
         del self._session[key]
         self.modified = True
         self.modified = True
 
 
+    @property
+    def key_salt(self):
+        return 'django.contrib.sessions.' + self.__class__.__qualname__
+
     def get(self, key, default=None):
     def get(self, key, default=None):
         return self._session.get(key, default)
         return self._session.get(key, default)
 
 
@@ -97,16 +102,27 @@ class SessionBase:
         del self[self.TEST_COOKIE_NAME]
         del self[self.TEST_COOKIE_NAME]
 
 
     def _hash(self, value):
     def _hash(self, value):
+        # RemovedInDjango40Warning: pre-Django 3.1 format will be invalid.
         key_salt = "django.contrib.sessions" + self.__class__.__name__
         key_salt = "django.contrib.sessions" + self.__class__.__name__
         return salted_hmac(key_salt, value).hexdigest()
         return salted_hmac(key_salt, value).hexdigest()
 
 
     def encode(self, session_dict):
     def encode(self, session_dict):
         "Return the given session dictionary serialized and encoded as a string."
         "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):
     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'))
         encoded_data = base64.b64decode(session_data.encode('ascii'))
         try:
         try:
             # could produce ValueError if there is no ':'
             # could produce ValueError if there is no ':'

+ 2 - 0
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
 * Support for the pre-Django 3.1 password reset tokens in the admin site (that
   use the SHA-1 hashing algorithm) will be removed.
   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
 * The ``get_request`` argument for
   ``django.utils.deprecation.MiddlewareMixin.__init__()`` will be required and
   ``django.utils.deprecation.MiddlewareMixin.__init__()`` will be required and
   won't accept ``None``.
   won't accept ``None``.

+ 4 - 0
docs/releases/3.1.txt

@@ -539,6 +539,10 @@ Miscellaneous
   from the format generated by older versions of Django. Support for the old
   from the format generated by older versions of Django. Support for the old
   format remains until Django 4.0.
   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:
 .. _removed-features-3.1:
 
 
 Features removed in 3.1
 Features removed in 3.1

+ 12 - 0
tests/sessions_tests/tests.py

@@ -311,6 +311,18 @@ class SessionTestsMixin:
         encoded = self.session.encode(data)
         encoded = self.session.encode(data)
         self.assertEqual(self.session.decode(encoded), 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):
     def test_decode_failure_logged_to_security(self):
         bad_encode = base64.b64encode(b'flaskdj:alkdjf').decode('ascii')
         bad_encode = base64.b64encode(b'flaskdj:alkdjf').decode('ascii')
         with self.assertLogs('django.security.SuspiciousSession', 'WARNING') as cm:
         with self.assertLogs('django.security.SuspiciousSession', 'WARNING') as cm: