diff --git a/gluon/globals.py b/gluon/globals.py index 26fba47c..7390a3f9 100644 --- a/gluon/globals.py +++ b/gluon/globals.py @@ -1075,6 +1075,16 @@ class Session(Storage): scookies['HttpOnly'] = True if self._secure: scookies['secure'] = True + if self._same_site is None: + # Using SameSite Lax Mode is the default + # You actually have to call session.samesite(False) if you really + # dont want the extra protection provided by the SameSite header + self._same_site = 'Lax' + if self._same_site: + if 'samesite' not in Cookie.Morsel._reserved: + # Python version 3.7 and lower needs this + Cookie.Morsel._reserved['samesite'] = 'SameSite' + scookies['samesite'] = self._same_site def clear_session_cookies(self): request = current.request @@ -1153,6 +1163,9 @@ class Session(Storage): def secure(self): self._secure = True + def samesite(self, mode='Lax'): + self._same_site = mode + def forget(self, response=None): self._close(response) self._forget = True @@ -1180,7 +1193,7 @@ class Session(Storage): def _unchanged(self, response): if response.session_new: - internal = ['_last_timestamp', '_secure', '_start_timestamp'] + internal = ['_last_timestamp', '_secure', '_start_timestamp', '_same_site'] for item in self.keys(): if item not in internal: return False diff --git a/gluon/tests/test_globals.py b/gluon/tests/test_globals.py index 666249fa..bd78e4e1 100644 --- a/gluon/tests/test_globals.py +++ b/gluon/tests/test_globals.py @@ -231,6 +231,25 @@ class testResponse(unittest.TestCase): cookie = str(current.response.cookies) self.assertTrue('httponly' not in cookie.lower()) + def test_cookies_samesite(self): + # Test Lax is the default mode + current = setup_clean_session() + current.session._fixup_before_save() + cookie = str(current.response.cookies) + self.assertTrue('samesite=lax' in cookie.lower()) + # Test you can disable samesite + current = setup_clean_session() + current.session.samesite(False) + current.session._fixup_before_save() + cookie = str(current.response.cookies) + self.assertTrue('samesite' not in cookie.lower()) + # Test you can change mode + current = setup_clean_session() + current.session.samesite('Strict') + current.session._fixup_before_save() + cookie = str(current.response.cookies) + self.assertTrue('samesite=strict' in cookie.lower()) + def test_include_meta(self): response = Response() response.meta[u'web2py'] = 'web2py'