[3.1.x] Fixed #31790 -- Fixed setting SameSite and Secure cookies flags in HttpResponse.delete_cookie().

Cookies with the "SameSite" flag set to None and without the "secure"
flag will be soon rejected by latest browser versions.

This affects sessions and messages cookies.
Backport of 240cbb63bf from master
This commit is contained in:
Mariusz Felisiak 2020-07-16 08:16:58 +02:00
parent ac699ba647
commit 3ca8cc0df1
10 changed files with 66 additions and 10 deletions

View File

@ -92,7 +92,11 @@ class CookieStorage(BaseStorage):
samesite=settings.SESSION_COOKIE_SAMESITE,
)
else:
response.delete_cookie(self.cookie_name, domain=settings.SESSION_COOKIE_DOMAIN)
response.delete_cookie(
self.cookie_name,
domain=settings.SESSION_COOKIE_DOMAIN,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
def _store(self, messages, response, remove_oldest=True, *args, **kwargs):
"""

View File

@ -42,6 +42,7 @@ class SessionMiddleware(MiddlewareMixin):
settings.SESSION_COOKIE_NAME,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
patch_vary_headers(response, ('Cookie',))
else:

View File

@ -210,13 +210,18 @@ class HttpResponseBase:
value = signing.get_cookie_signer(salt=key + salt).sign(value)
return self.set_cookie(key, value, **kwargs)
def delete_cookie(self, key, path='/', domain=None):
# Most browsers ignore the Set-Cookie header if the cookie name starts
# with __Host- or __Secure- and the cookie doesn't use the secure flag.
secure = key.startswith(('__Secure-', '__Host-'))
def delete_cookie(self, key, path='/', domain=None, samesite=None):
# Browsers can ignore the Set-Cookie header if the cookie doesn't use
# the secure flag and:
# - the cookie name starts with "__Host-" or "__Secure-", or
# - the samesite is "none".
secure = (
key.startswith(('__Secure-', '__Host-')) or
(samesite and samesite.lower() == 'none')
)
self.set_cookie(
key, max_age=0, path=path, domain=domain, secure=secure,
expires='Thu, 01 Jan 1970 00:00:00 GMT',
expires='Thu, 01 Jan 1970 00:00:00 GMT', samesite=samesite,
)
# Common methods used by subclasses

View File

@ -902,7 +902,7 @@ Methods
Using ``samesite='None'`` (string) was allowed.
.. method:: HttpResponse.delete_cookie(key, path='/', domain=None)
.. method:: HttpResponse.delete_cookie(key, path='/', domain=None, samesite=None)
Deletes the cookie with the given key. Fails silently if the key doesn't
exist.
@ -911,6 +911,10 @@ Methods
values you used in ``set_cookie()`` -- otherwise the cookie may not be
deleted.
.. versionchanged:: 2.2.15
The ``samesite`` argument was added.
.. method:: HttpResponse.close()
This method is called at the end of the request directly by the WSGI

17
docs/releases/2.2.15.txt Normal file
View File

@ -0,0 +1,17 @@
===========================
Django 2.2.15 release notes
===========================
*Expected August 3, 2020*
Django 2.2.15 fixes a bug in 2.2.14.
Bugfixes
========
* Allowed setting the ``SameSite`` cookie flag in
:meth:`.HttpResponse.delete_cookie` (:ticket:`31790`).
* Fixed setting the ``Secure`` cookie flag in
:meth:`.HttpResponse.delete_cookie` for cookies that use ``samesite='none'``
(:ticket:`31790`).

View File

@ -9,4 +9,9 @@ Django 3.0.9 fixes several bugs in 3.0.8.
Bugfixes
========
* ...
* Allowed setting the ``SameSite`` cookie flag in
:meth:`.HttpResponse.delete_cookie` (:ticket:`31790`).
* Fixed setting the ``Secure`` cookie flag in
:meth:`.HttpResponse.delete_cookie` for cookies that use ``samesite='none'``
(:ticket:`31790`).

View File

@ -48,6 +48,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1
2.2.15
2.2.14
2.2.13
2.2.12

View File

@ -1,5 +1,6 @@
import json
from django.conf import settings
from django.contrib.messages import constants
from django.contrib.messages.storage.base import Message
from django.contrib.messages.storage.cookie import (
@ -85,6 +86,10 @@ class CookieTests(BaseTests, SimpleTestCase):
self.assertEqual(response.cookies['messages'].value, '')
self.assertEqual(response.cookies['messages']['domain'], '.example.com')
self.assertEqual(response.cookies['messages']['expires'], 'Thu, 01 Jan 1970 00:00:00 GMT')
self.assertEqual(
response.cookies['messages']['samesite'],
settings.SESSION_COOKIE_SAMESITE,
)
def test_get_bad_cookie(self):
request = self.get_request()

View File

@ -105,6 +105,7 @@ class DeleteCookieTests(SimpleTestCase):
self.assertEqual(cookie['path'], '/')
self.assertEqual(cookie['secure'], '')
self.assertEqual(cookie['domain'], '')
self.assertEqual(cookie['samesite'], '')
def test_delete_cookie_secure_prefix(self):
"""
@ -118,3 +119,14 @@ class DeleteCookieTests(SimpleTestCase):
cookie_name = '__%s-c' % prefix
response.delete_cookie(cookie_name)
self.assertIs(response.cookies[cookie_name]['secure'], True)
def test_delete_cookie_secure_samesite_none(self):
# delete_cookie() sets the secure flag if samesite='none'.
response = HttpResponse()
response.delete_cookie('c', samesite='none')
self.assertIs(response.cookies['c']['secure'], True)
def test_delete_cookie_samesite(self):
response = HttpResponse()
response.delete_cookie('c', samesite='lax')
self.assertEqual(response.cookies['c']['samesite'], 'lax')

View File

@ -758,8 +758,9 @@ class SessionMiddlewareTests(TestCase):
# Set-Cookie: sessionid=; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/
self.assertEqual(
'Set-Cookie: {}=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; '
'Max-Age=0; Path=/'.format(
'Max-Age=0; Path=/; SameSite={}'.format(
settings.SESSION_COOKIE_NAME,
settings.SESSION_COOKIE_SAMESITE,
),
str(response.cookies[settings.SESSION_COOKIE_NAME])
)
@ -789,8 +790,9 @@ class SessionMiddlewareTests(TestCase):
# Path=/example/
self.assertEqual(
'Set-Cookie: {}=""; Domain=.example.local; expires=Thu, '
'01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/example/'.format(
'01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/example/; SameSite={}'.format(
settings.SESSION_COOKIE_NAME,
settings.SESSION_COOKIE_SAMESITE,
),
str(response.cookies[settings.SESSION_COOKIE_NAME])
)