diff --git a/CHANGES/4224.feature b/CHANGES/4224.feature new file mode 100644 index 00000000000..a2427099b08 --- /dev/null +++ b/CHANGES/4224.feature @@ -0,0 +1 @@ +allow use of SameSite in cookies. diff --git a/aiohttp/web_response.py b/aiohttp/web_response.py index b1095b94cc0..088ce55151d 100644 --- a/aiohttp/web_response.py +++ b/aiohttp/web_response.py @@ -9,7 +9,7 @@ import zlib from concurrent.futures import Executor from email.utils import parsedate -from http.cookies import SimpleCookie +from http.cookies import Morsel, SimpleCookie from typing import ( # noqa TYPE_CHECKING, Any, @@ -27,7 +27,7 @@ from . import hdrs, payload from .abc import AbstractStreamWriter -from .helpers import HeadersMixin, rfc822_formatted_time, sentinel +from .helpers import PY_38, HeadersMixin, rfc822_formatted_time, sentinel from .http import RESPONSES, SERVER_SOFTWARE, HttpVersion10, HttpVersion11 from .payload import Payload from .typedefs import JSONEncoder, LooseHeaders @@ -42,6 +42,12 @@ BaseClass = collections.abc.MutableMapping +if not PY_38: + # allow samesite to be used in python < 3.8 + # already permitted in python 3.8, see https://bugs.python.org/issue29613 + Morsel._reserved['samesite'] = 'SameSite' # type: ignore + + class ContentCoding(enum.Enum): # The content codings that we have support for. # @@ -183,7 +189,8 @@ def set_cookie(self, name: str, value: str, *, path: str='/', secure: Optional[bool]=None, httponly: Optional[bool]=None, - version: Optional[str]=None) -> None: + version: Optional[str]=None, + samesite: Optional[str]=None) -> None: """Set or update response cookie. Sets new cookie or updates existent with new value. @@ -219,6 +226,8 @@ def set_cookie(self, name: str, value: str, *, c['httponly'] = httponly if version is not None: c['version'] = version + if samesite is not None: + c['samesite'] = samesite def del_cookie(self, name: str, *, domain: Optional[str]=None, diff --git a/docs/web_reference.rst b/docs/web_reference.rst index 0e2263721ea..1bd55dd7f30 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -689,7 +689,8 @@ StreamResponse .. method:: set_cookie(name, value, *, path='/', expires=None, \ domain=None, max_age=None, \ - secure=None, httponly=None, version=None) + secure=None, httponly=None, version=None, \ + samesite=None) Convenient way for setting :attr:`cookies`, allows to specify some additional properties like *max_age* in a single call. @@ -734,6 +735,14 @@ StreamResponse specification the cookie conforms. (Optional, *version=1* by default) + :param str samesite: Asserts that a cookie must not be sent with + cross-origin requests, providing some protection + against cross-site request forgery attacks. + Generally the value should be one of: ``None``, + ``Lax`` or ``Strict``. (optional) + + .. versionadded:: 3.7 + .. warning:: In HTTP version 1.1, ``expires`` was deprecated and replaced with diff --git a/tests/test_web_response.py b/tests/test_web_response.py index 70eb35b769d..d269cfafc3a 100644 --- a/tests/test_web_response.py +++ b/tests/test_web_response.py @@ -703,13 +703,14 @@ def test_response_cookie_path() -> None: 'Set-Cookie: name=value; expires=123; Path=/') resp.set_cookie('name', 'value', domain='example.com', path='/home', expires='123', max_age='10', - secure=True, httponly=True, version='2.0') + secure=True, httponly=True, version='2.0', samesite='lax') assert (str(resp.cookies).lower() == 'set-cookie: name=value; ' 'domain=example.com; ' 'expires=123; ' 'httponly; ' 'max-age=10; ' 'path=/home; ' + 'samesite=lax; ' 'secure; ' 'version=2.0')