diff options
| author | Claude Paroz <claude@2xlibre.net> | 2020-01-30 23:11:09 +0100 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2020-02-04 08:05:02 +0100 |
| commit | 8ae84156d62bfc24d71e65cfe4d5cb84b9b1bd91 (patch) | |
| tree | 5775472d0351363c9748be5eec171da3441c7162 | |
| parent | bcc9fa25285f506666fa5074fc43c7114d61bb79 (diff) | |
Fixed #27604 -- Used the cookie signer to sign message cookies.
Co-authored-by: Craig Anderson <craiga@craiga.id.au>
| -rw-r--r-- | django/contrib/messages/storage/cookie.py | 41 | ||||
| -rw-r--r-- | docs/internals/deprecation.txt | 3 | ||||
| -rw-r--r-- | docs/releases/3.1.txt | 5 | ||||
| -rw-r--r-- | tests/messages_tests/test_cookie.py | 11 |
4 files changed, 48 insertions, 12 deletions
diff --git a/django/contrib/messages/storage/cookie.py b/django/contrib/messages/storage/cookie.py index eb7d6d6bc5..5f0366fb19 100644 --- a/django/contrib/messages/storage/cookie.py +++ b/django/contrib/messages/storage/cookie.py @@ -2,6 +2,7 @@ import json from django.conf import settings from django.contrib.messages.storage.base import BaseStorage, Message +from django.core import signing from django.http import SimpleCookie from django.utils.crypto import constant_time_compare, salted_hmac from django.utils.safestring import SafeData, mark_safe @@ -58,6 +59,10 @@ class CookieStorage(BaseStorage): not_finished = '__messagesnotfinished__' key_salt = 'django.contrib.messages' + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.signer = signing.get_cookie_signer(salt=self.key_salt) + def _get(self, *args, **kwargs): """ Retrieve a list of messages from the messages cookie. If the @@ -118,8 +123,9 @@ class CookieStorage(BaseStorage): self._update_cookie(encoded_data, response) return unstored_messages - def _hash(self, value): + def _legacy_hash(self, value): """ + # RemovedInDjango40Warning: pre-Django 3.1 hashes will be invalid. Create an HMAC/SHA1 hash based on the value and the project setting's SECRET_KEY, modified to make it unique for the present purpose. """ @@ -136,7 +142,7 @@ class CookieStorage(BaseStorage): if messages or encode_empty: encoder = MessageEncoder(separators=(',', ':')) value = encoder.encode(messages) - return '%s$%s' % (self._hash(value), value) + return self.signer.sign(value) def _decode(self, data): """ @@ -147,17 +153,28 @@ class CookieStorage(BaseStorage): """ if not data: return None - bits = data.split('$', 1) - if len(bits) == 2: - hash, value = bits - if constant_time_compare(hash, self._hash(value)): - try: - # If we get here (and the JSON decode works), everything is - # good. In any other case, drop back and return None. - return json.loads(value, cls=MessageDecoder) - except json.JSONDecodeError: - pass + try: + decoded = self.signer.unsign(data) + except signing.BadSignature: + # RemovedInDjango40Warning: when the deprecation ends, replace + # with: + # decoded = None. + decoded = self._legacy_decode(data) + if decoded: + try: + return json.loads(decoded, cls=MessageDecoder) + except json.JSONDecodeError: + pass # Mark the data as used (so it gets removed) since something was wrong # with the data. self.used = True return None + + def _legacy_decode(self, data): + # RemovedInDjango40Warning: pre-Django 3.1 hashes will be invalid. + bits = data.split('$', 1) + if len(bits) == 2: + hash_, value = bits + if constant_time_compare(hash_, self._legacy_hash(value)): + return value + return None diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index dc6923d488..db6df0fbbe 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -46,6 +46,9 @@ details on these changes. * The ``HttpRequest.is_ajax()`` method will be removed. +* Support for the pre-Django 3.1 encoding format of cookies values used by + ``django.contrib.messages.storage.cookie.CookieStorage`` will be removed. + See the :ref:`Django 3.1 release notes <deprecated-features-3.1>` for more details on these changes. diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index 40380b6274..a09da6dd30 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -482,6 +482,11 @@ Miscellaneous the new :meth:`.HttpRequest.accepts` method if your code depends on the client ``Accept`` HTTP header. +* The encoding format of cookies values used by + :class:`~django.contrib.messages.storage.cookie.CookieStorage` 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/messages_tests/test_cookie.py b/tests/messages_tests/test_cookie.py index 48c928cb9c..5675cd15eb 100644 --- a/tests/messages_tests/test_cookie.py +++ b/tests/messages_tests/test_cookie.py @@ -153,3 +153,14 @@ class CookieTests(BaseTests, SimpleTestCase): storage = self.get_storage() self.assertIsInstance(encode_decode(mark_safe("<b>Hello Django!</b>")), SafeData) self.assertNotIsInstance(encode_decode("<b>Hello Django!</b>"), SafeData) + + def test_legacy_hash_decode(self): + # RemovedInDjango40Warning: pre-Django 3.1 hashes will be invalid. + storage = self.storage_class(self.get_request()) + messages = ['this', 'that'] + # Encode/decode a message using the pre-Django 3.1 hash. + encoder = MessageEncoder(separators=(',', ':')) + value = encoder.encode(messages) + encoded_messages = '%s$%s' % (storage._legacy_hash(value), value) + decoded_messages = storage._decode(encoded_messages) + self.assertEqual(messages, decoded_messages) |
