import unittest from contextlib import contextmanager from distutils.version import LooseVersion from mock import patch import django from ddt import ddt, data from django.test import TestCase from django_cookies_samesite.middleware import DJANGO_SUPPORTED_VERSION, CookiesSameSite @ddt class CookieSamesiteConfigTests(TestCase): def test_settings_default_values(self): """Check if middleware reads default values as expected""" with self.settings(): middleware = CookiesSameSite() self.assertEqual(middleware.samesite_force_all, None) self.assertEqual(middleware.protected_cookies, {'sessionid', 'csrftoken'}) if LooseVersion(django.get_version()) >= LooseVersion('3.0'): self.assertEqual(middleware.samesite_flag, 'Lax') else: self.assertEqual(middleware.samesite_flag, '') @data( '', 'DCS_' ) def test_settings(self, config_prefix): """ Check if the cookie middleware fetches prefixed settings. """ with self.settings(**{ "{}SESSION_COOKIE_SAMESITE".format(config_prefix): 'Lax', "{}SESSION_COOKIE_SAMESITE_FORCE_ALL".format(config_prefix): True, "{}SESSION_COOKIE_SAMESITE_KEYS".format(config_prefix): {'custom_cookie'}, }): middleware = CookiesSameSite() self.assertEqual(middleware.samesite_flag, 'Lax') self.assertEqual(middleware.samesite_force_all, True) self.assertEqual(middleware.protected_cookies, {'sessionid', 'csrftoken', 'custom_cookie'}) @ddt class CookiesSamesiteTestsWithConfigPrefix(TestCase): config_prefix = "DCS_" @contextmanager def settings(self, **config_settings): """Override all settings with the prefix name""" def format_key(k): """Prefix only the middleware settings.""" return "{}{}".format(self.config_prefix, k) if k.startswith("SESSION_COOKIE_SAMESITE") else k prefixed_settings = { format_key(k): v for k, v in config_settings.items() } with super(CookiesSamesiteTestsWithConfigPrefix, self).settings(**prefixed_settings): yield @unittest.skipIf(django.get_version() >= DJANGO_SUPPORTED_VERSION, 'should skip if Django already supports') def test_cookie_samesite_Strict(self): with self.settings(SESSION_COOKIE_SAMESITE='Strict'): response = self.client.get('/cookies-test/') self.assertEqual(response.cookies['sessionid']['samesite'], 'Strict') self.assertEqual(response.cookies['csrftoken']['samesite'], 'Strict') cookies_string = sorted(response.cookies.output().split('\r\n')) self.assertTrue('csrftoken=', cookies_string[0]) self.assertTrue('; SameSite=Strict', cookies_string[0]) self.assertTrue('sessionid=', cookies_string[2]) self.assertTrue('; SameSite=Strict', cookies_string[2]) @unittest.skipIf(django.get_version() >= DJANGO_SUPPORTED_VERSION, 'should skip if Django already supports') def test_cookie_samesite_Lax(self): with self.settings(SESSION_COOKIE_SAMESITE='Lax'): response = self.client.get('/cookies-test/') self.assertEqual(response.cookies['sessionid']['samesite'], 'Lax') self.assertEqual(response.cookies['csrftoken']['samesite'], 'Lax') cookies_string = sorted(response.cookies.output().split('\r\n')) self.assertTrue('csrftoken=' in cookies_string[0]) self.assertTrue('; SameSite=Lax' in cookies_string[0]) self.assertTrue('sessionid=' in cookies_string[2]) self.assertTrue('; SameSite=Lax' in cookies_string[2]) @unittest.skipIf(django.get_version() >= DJANGO_SUPPORTED_VERSION, 'should skip if Django already supports') def test_cookie_samesite_none(self): with self.settings(SESSION_COOKIE_SAMESITE='None'): response = self.client.get('/cookies-test/') self.assertEqual(response.cookies['sessionid']['samesite'], 'None') self.assertEqual(response.cookies['csrftoken']['samesite'], 'None') cookies_string = sorted(response.cookies.output().split('\r\n')) self.assertTrue('csrftoken=' in cookies_string[0]) self.assertTrue('; SameSite=None' in cookies_string[0]) self.assertTrue('sessionid=' in cookies_string[2]) self.assertTrue('; SameSite=None' in cookies_string[2]) @unittest.skipIf(django.get_version() >= DJANGO_SUPPORTED_VERSION, 'should skip if Django already supports') def test_cookie_samesite_none_force_all(self): with self.settings(SESSION_COOKIE_SAMESITE='None', SESSION_COOKIE_SAMESITE_FORCE_ALL=True): response = self.client.get('/cookies-test/') self.assertEqual(response.cookies['sessionid']['samesite'], 'None') self.assertEqual(response.cookies['csrftoken']['samesite'], 'None') self.assertEqual(response.cookies['custom_cookie']['samesite'], 'None') self.assertEqual(response.cookies['zcustom_cookie']['samesite'], 'None') cookies_string = sorted(response.cookies.output().split('\r\n')) self.assertTrue('custom_cookie=' in cookies_string[1]) self.assertTrue('; SameSite=None' in cookies_string[1]) self.assertTrue('csrftoken=' in cookies_string[0]) self.assertTrue('; SameSite=None' in cookies_string[0]) self.assertTrue('sessionid=' in cookies_string[2]) self.assertTrue('; SameSite=None' in cookies_string[2]) self.assertTrue('zcustom_cookie=' in cookies_string[3]) self.assertTrue('; SameSite=None' in cookies_string[3]) @unittest.skipIf(django.get_version() < DJANGO_SUPPORTED_VERSION, 'should skip if Django does not support') def test_cookie_samesite_django30(self): # Raise DeprecationWarning for newer versions of Django with patch('django.get_version', return_value=DJANGO_SUPPORTED_VERSION): with self.assertRaises(DeprecationWarning) as exc: self.client.get('/cookies-test/') self.assertEqual(exc.exception.args[0], ( 'Your version of Django supports SameSite flag in the cookies mechanism. ' 'You should remove django-cookies-samesite from your project.' )) with patch('django_cookies_samesite.middleware.django.get_version', return_value=DJANGO_SUPPORTED_VERSION): with self.assertRaises(DeprecationWarning) as exc: self.client.get('/cookies-test/') self.assertEqual(exc.exception.args[0], ( 'Your version of Django supports SameSite flag in the cookies mechanism. ' 'You should remove django-cookies-samesite from your project.' )) @unittest.skipIf(django.get_version() >= DJANGO_SUPPORTED_VERSION, 'should skip if Django already supports') def test_cookie_samesite_custom_cookies(self): # Middleware shouldn't accept malformed settings with self.settings( SESSION_COOKIE_SAMESITE='Lax', SESSION_COOKIE_SAMESITE_KEYS='something' ): with self.assertRaises(ValueError) as exc: self.client.get('/cookies-test/') self.assertEqual(exc.exception.args[0], 'SESSION_COOKIE_SAMESITE_KEYS should be a list, set or tuple.') # Test if SameSite flags is set to custom cookies with self.settings( SESSION_COOKIE_SAMESITE='Lax', SESSION_COOKIE_SAMESITE_KEYS=('custom_cookie',) ): response = self.client.get('/cookies-test/') self.assertEqual(response.cookies['sessionid']['samesite'], 'Lax') self.assertEqual(response.cookies['csrftoken']['samesite'], 'Lax') self.assertEqual(response.cookies['custom_cookie']['samesite'], 'Lax') self.assertEqual(response.cookies['zcustom_cookie']['samesite'], '') cookies_string = sorted(response.cookies.output().split('\r\n')) self.assertTrue('custom_cookie=' in cookies_string[1]) self.assertTrue('; SameSite=Lax' in cookies_string[1]) self.assertTrue('csrftoken=' in cookies_string[0]) self.assertTrue('; SameSite=Lax' in cookies_string[0]) self.assertTrue('sessionid=' in cookies_string[2]) self.assertTrue('; SameSite=Lax' in cookies_string[2]) self.assertTrue('zcustom_cookie=' in cookies_string[3]) self.assertTrue('; SameSite=Lax' not in cookies_string[3]) @unittest.skipIf(django.get_version() >= DJANGO_SUPPORTED_VERSION, 'should skip if Django already supports') def test_cookie_samesite_invalid(self): with self.settings(SESSION_COOKIE_SAMESITE='invalid'): with self.assertRaises(ValueError) as exc: self.client.get('/cookies-test/') self.assertEqual(exc.exception.args[0], 'samesite must be "Lax", "None", or "Strict".') @unittest.skipIf(django.get_version() >= DJANGO_SUPPORTED_VERSION, 'should skip if Django already supports') def test_cookie_samesite_unset(self): with self.settings(SESSION_COOKIE_SAMESITE=None): response = self.client.get('/cookies-test/') self.assertEqual(response.cookies['sessionid'].get('samesite'), '') self.assertEqual(response.cookies['csrftoken'].get('samesite'), '') cookies_string = sorted(response.cookies.output().split('\r\n')) self.assertTrue('csrftoken=' in cookies_string[0]) self.assertTrue('; SameSite=Lax' not in cookies_string[0]) self.assertTrue('; SameSite=Strict' not in cookies_string[0]) self.assertTrue('; SameSite=None' not in cookies_string[0]) self.assertTrue('sessionid=' in cookies_string[2]) self.assertTrue('; SameSite=Lax' not in cookies_string[2]) self.assertTrue('; SameSite=None' not in cookies_string[2]) @unittest.skipIf(django.get_version() >= DJANGO_SUPPORTED_VERSION, 'should skip if Django already supports') def test_cookie_names_changed(self): session_name = 'sessionid-test' csrf_name = 'csrftoken-test' with self.settings( SESSION_COOKIE_NAME=session_name, CSRF_COOKIE_NAME=csrf_name, SESSION_COOKIE_SAMESITE='Lax' ): response = self.client.get('/cookies-test/') self.assertEqual(response.cookies[session_name]['samesite'], 'Lax') self.assertEqual(response.cookies[csrf_name]['samesite'], 'Lax') cookies_string = sorted(response.cookies.output().split('\r\n')) self.assertTrue(csrf_name + '=' in cookies_string[0]) self.assertTrue('; SameSite=Lax' in cookies_string[0]) self.assertTrue(session_name + '=' in cookies_string[2]) self.assertTrue('; SameSite=Lax' in cookies_string[2]) @unittest.skipIf(django.get_version() >= DJANGO_SUPPORTED_VERSION, 'should skip if Django already supports') @data( # Chrome "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", ) def test_unsupported_browsers(self, ua_string): session_name = 'sessionid-test' csrf_name = 'csrftoken-test' with self.settings( SESSION_COOKIE_NAME=session_name, CSRF_COOKIE_NAME=csrf_name, SESSION_COOKIE_SAMESITE='Lax' ): response = self.client.get( '/cookies-test/', HTTP_USER_AGENT=ua_string, ) self.assertEqual(response.cookies[session_name]['samesite'], '') self.assertEqual(response.cookies[csrf_name]['samesite'], '') cookies_string = sorted(response.cookies.output().split('\r\n')) self.assertTrue('; SameSite=Lax' not in cookies_string[0]) self.assertTrue('; SameSite=Lax' not in cookies_string[1]) @data( # Chrome 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.2704.103 Safari/537.36', # Firefox "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0", # Internet Explorer "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0)", # Safari "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) " "Version/10.0 Mobile/14E304 Safari/602.1 " ) @unittest.skipIf(django.get_version() >= DJANGO_SUPPORTED_VERSION, 'should skip if Django already supports') def test_supported_browsers(self, ua_string): session_name = 'sessionid-test' csrf_name = 'csrftoken-test' with self.settings( SESSION_COOKIE_NAME=session_name, CSRF_COOKIE_NAME=csrf_name, SESSION_COOKIE_SAMESITE='Lax' ): response = self.client.get( '/cookies-test/', HTTP_USER_AGENT=ua_string, ) self.assertEqual(response.cookies[session_name]['samesite'], 'Lax') self.assertEqual(response.cookies[csrf_name]['samesite'], 'Lax') cookies_string = sorted(response.cookies.output().split('\r\n')) self.assertTrue('; SameSite=Lax' in cookies_string[0]) self.assertTrue('; SameSite=Lax' in cookies_string[2]) @data( # Chrome "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.2704.103 Safari/537.36", # Firefox "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0", # Internet Explorer "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0)", # Safari "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) " "Version/10.0 Mobile/14E304 Safari/602.1 " # noqa ) @unittest.skipIf(django.get_version() >= DJANGO_SUPPORTED_VERSION, 'should skip if Django already supports') def test_supported_browsers_with_secure_true(self, ua_string): session_name = 'sessionid-test' csrf_name = 'csrftoken-test' with self.settings( SESSION_COOKIE_NAME=session_name, CSRF_COOKIE_NAME=csrf_name, SESSION_COOKIE_SAMESITE='None' ): response = self.client.get( '/cookies-test/', HTTP_USER_AGENT=ua_string, secure=True, ) self.assertEqual(response.cookies[session_name]['samesite'], 'None') self.assertEqual(response.cookies[session_name]['secure'], True) self.assertEqual(response.cookies[csrf_name]['samesite'], 'None') self.assertEqual(response.cookies[csrf_name]['secure'], True) cookies_string = sorted(response.cookies.output().split('\r\n')) self.assertTrue('; SameSite=None; Secure' in cookies_string[0]) self.assertTrue('; SameSite=None; Secure' in cookies_string[2]) @ddt class CookiesSamesiteTestsWithoutConfigPrefix(TestCase): """Check if the middleware works in the same way if settings don't have DCS_ prefix.""" config_prefix = ""