summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2020-01-30 23:11:09 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-02-04 08:05:02 +0100
commit8ae84156d62bfc24d71e65cfe4d5cb84b9b1bd91 (patch)
tree5775472d0351363c9748be5eec171da3441c7162
parentbcc9fa25285f506666fa5074fc43c7114d61bb79 (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.py41
-rw-r--r--docs/internals/deprecation.txt3
-rw-r--r--docs/releases/3.1.txt5
-rw-r--r--tests/messages_tests/test_cookie.py11
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)