mirror of
https://github.com/django/django.git
synced 2025-06-05 03:29:12 +00:00
Fixed #27604 -- Used the cookie signer to sign message cookies.
Co-authored-by: Craig Anderson <craiga@craiga.id.au>
This commit is contained in:
parent
bcc9fa2528
commit
8ae84156d6
@ -2,6 +2,7 @@ import json
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.messages.storage.base import BaseStorage, Message
|
from django.contrib.messages.storage.base import BaseStorage, Message
|
||||||
|
from django.core import signing
|
||||||
from django.http import SimpleCookie
|
from django.http import SimpleCookie
|
||||||
from django.utils.crypto import constant_time_compare, salted_hmac
|
from django.utils.crypto import constant_time_compare, salted_hmac
|
||||||
from django.utils.safestring import SafeData, mark_safe
|
from django.utils.safestring import SafeData, mark_safe
|
||||||
@ -58,6 +59,10 @@ class CookieStorage(BaseStorage):
|
|||||||
not_finished = '__messagesnotfinished__'
|
not_finished = '__messagesnotfinished__'
|
||||||
key_salt = 'django.contrib.messages'
|
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):
|
def _get(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Retrieve a list of messages from the messages cookie. If the
|
Retrieve a list of messages from the messages cookie. If the
|
||||||
@ -118,8 +123,9 @@ class CookieStorage(BaseStorage):
|
|||||||
self._update_cookie(encoded_data, response)
|
self._update_cookie(encoded_data, response)
|
||||||
return unstored_messages
|
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
|
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.
|
SECRET_KEY, modified to make it unique for the present purpose.
|
||||||
"""
|
"""
|
||||||
@ -136,7 +142,7 @@ class CookieStorage(BaseStorage):
|
|||||||
if messages or encode_empty:
|
if messages or encode_empty:
|
||||||
encoder = MessageEncoder(separators=(',', ':'))
|
encoder = MessageEncoder(separators=(',', ':'))
|
||||||
value = encoder.encode(messages)
|
value = encoder.encode(messages)
|
||||||
return '%s$%s' % (self._hash(value), value)
|
return self.signer.sign(value)
|
||||||
|
|
||||||
def _decode(self, data):
|
def _decode(self, data):
|
||||||
"""
|
"""
|
||||||
@ -147,17 +153,28 @@ class CookieStorage(BaseStorage):
|
|||||||
"""
|
"""
|
||||||
if not data:
|
if not data:
|
||||||
return None
|
return None
|
||||||
bits = data.split('$', 1)
|
try:
|
||||||
if len(bits) == 2:
|
decoded = self.signer.unsign(data)
|
||||||
hash, value = bits
|
except signing.BadSignature:
|
||||||
if constant_time_compare(hash, self._hash(value)):
|
# RemovedInDjango40Warning: when the deprecation ends, replace
|
||||||
try:
|
# with:
|
||||||
# If we get here (and the JSON decode works), everything is
|
# decoded = None.
|
||||||
# good. In any other case, drop back and return None.
|
decoded = self._legacy_decode(data)
|
||||||
return json.loads(value, cls=MessageDecoder)
|
if decoded:
|
||||||
except json.JSONDecodeError:
|
try:
|
||||||
pass
|
return json.loads(decoded, cls=MessageDecoder)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
# Mark the data as used (so it gets removed) since something was wrong
|
# Mark the data as used (so it gets removed) since something was wrong
|
||||||
# with the data.
|
# with the data.
|
||||||
self.used = True
|
self.used = True
|
||||||
return None
|
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
|
||||||
|
@ -46,6 +46,9 @@ details on these changes.
|
|||||||
|
|
||||||
* The ``HttpRequest.is_ajax()`` method will be removed.
|
* 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
|
See the :ref:`Django 3.1 release notes <deprecated-features-3.1>` for more
|
||||||
details on these changes.
|
details on these changes.
|
||||||
|
|
||||||
|
@ -482,6 +482,11 @@ Miscellaneous
|
|||||||
the new :meth:`.HttpRequest.accepts` method if your code depends on the
|
the new :meth:`.HttpRequest.accepts` method if your code depends on the
|
||||||
client ``Accept`` HTTP header.
|
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:
|
.. _removed-features-3.1:
|
||||||
|
|
||||||
Features removed in 3.1
|
Features removed in 3.1
|
||||||
|
@ -153,3 +153,14 @@ class CookieTests(BaseTests, SimpleTestCase):
|
|||||||
storage = self.get_storage()
|
storage = self.get_storage()
|
||||||
self.assertIsInstance(encode_decode(mark_safe("<b>Hello Django!</b>")), SafeData)
|
self.assertIsInstance(encode_decode(mark_safe("<b>Hello Django!</b>")), SafeData)
|
||||||
self.assertNotIsInstance(encode_decode("<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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user