1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

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

Thanks Mariusz Felisiak and Florian Apolloner for the reviews.
This commit is contained in:
Claude Paroz 2020-02-15 12:20:37 +01:00 committed by Mariusz Felisiak
parent daaa894960
commit d4fff711d4
4 changed files with 37 additions and 3 deletions

View File

@ -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) return signing.dumps(
hash = self._hash(serialized) session_dict, salt=self.key_salt, serializer=self.serializer,
return base64.b64encode(hash.encode() + b":" + serialized).decode('ascii') 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 ':'

View File

@ -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``.

View File

@ -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

View File

@ -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: