mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +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:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							bcc9fa2528
						
					
				
				
					commit
					8ae84156d6
				
			| @@ -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 | ||||
|   | ||||
| @@ -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. | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user