New Feature: Added Support for cookie's SameSite attribute.
This commit is contained in:
parent
22708b048d
commit
a1d9ebe4ab
5 changed files with 61 additions and 4 deletions
|
|
@ -208,6 +208,14 @@ The following configuration values are used internally by Flask:
|
||||||
|
|
||||||
Default: ``False``
|
Default: ``False``
|
||||||
|
|
||||||
|
.. py:data:: SESSION_COOKIE_SAMESITE
|
||||||
|
|
||||||
|
Browser will only send cookies to the domain that created them.
|
||||||
|
There are two possible values for the same-site attribute: "Strict" and "Lax"
|
||||||
|
If set to "None", the samesite flag is not set.
|
||||||
|
|
||||||
|
Default: ``None``
|
||||||
|
|
||||||
.. py:data:: PERMANENT_SESSION_LIFETIME
|
.. py:data:: PERMANENT_SESSION_LIFETIME
|
||||||
|
|
||||||
If ``session.permanent`` is true, the cookie's expiration will be set this
|
If ``session.permanent`` is true, the cookie's expiration will be set this
|
||||||
|
|
@ -635,4 +643,3 @@ Example usage for both::
|
||||||
# or via open_instance_resource:
|
# or via open_instance_resource:
|
||||||
with app.open_instance_resource('application.cfg') as f:
|
with app.open_instance_resource('application.cfg') as f:
|
||||||
config = f.read()
|
config = f.read()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -195,16 +195,18 @@ They can be set on other cookies too.
|
||||||
- ``HttpOnly`` protects the contents of cookies from being read with
|
- ``HttpOnly`` protects the contents of cookies from being read with
|
||||||
JavaScript.
|
JavaScript.
|
||||||
- ``SameSite`` ensures that cookies can only be requested from the same
|
- ``SameSite`` ensures that cookies can only be requested from the same
|
||||||
domain that created them. It is not supported by Flask yet.
|
domain that created them. There are two possible values for the same-site
|
||||||
|
attribute: "Strict" and "Lax"
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
app.config.update(
|
app.config.update(
|
||||||
SESSION_COOKIE_SECURE=True,
|
SESSION_COOKIE_SECURE=True,
|
||||||
SESSION_COOKIE_HTTPONLY=True,
|
SESSION_COOKIE_HTTPONLY=True,
|
||||||
|
SESSION_COOKIE_SAMESITE='Strict'
|
||||||
)
|
)
|
||||||
|
|
||||||
response.set_cookie('username', 'flask', secure=True, httponly=True)
|
response.set_cookie('username', 'flask', secure=True, httponly=True, samesite='Strict')
|
||||||
|
|
||||||
Specifying ``Expires`` or ``Max-Age`` options, will remove the cookie after
|
Specifying ``Expires`` or ``Max-Age`` options, will remove the cookie after
|
||||||
the given time, or the current time plus the age, respectively. If neither
|
the given time, or the current time plus the age, respectively. If neither
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,7 @@ class Flask(_PackageBoundObject):
|
||||||
'SESSION_COOKIE_PATH': None,
|
'SESSION_COOKIE_PATH': None,
|
||||||
'SESSION_COOKIE_HTTPONLY': True,
|
'SESSION_COOKIE_HTTPONLY': True,
|
||||||
'SESSION_COOKIE_SECURE': False,
|
'SESSION_COOKIE_SECURE': False,
|
||||||
|
'SESSION_COOKIE_SAMESITE': None,
|
||||||
'SESSION_REFRESH_EACH_REQUEST': True,
|
'SESSION_REFRESH_EACH_REQUEST': True,
|
||||||
'MAX_CONTENT_LENGTH': None,
|
'MAX_CONTENT_LENGTH': None,
|
||||||
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
|
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,13 @@ class SessionInterface(object):
|
||||||
"""
|
"""
|
||||||
return app.config['SESSION_COOKIE_SECURE']
|
return app.config['SESSION_COOKIE_SECURE']
|
||||||
|
|
||||||
|
def get_cookie_samesite(self, app):
|
||||||
|
"""Returns "Strict", "Lax" or None if the cookie should use
|
||||||
|
samesite attribute. This currently just returns the value of
|
||||||
|
the ``SESSION_COOKIE_SAMESITE`` setting.
|
||||||
|
"""
|
||||||
|
return app.config['SESSION_COOKIE_SAMESITE']
|
||||||
|
|
||||||
def get_expiration_time(self, app, session):
|
def get_expiration_time(self, app, session):
|
||||||
"""A helper method that returns an expiration date for the session
|
"""A helper method that returns an expiration date for the session
|
||||||
or ``None`` if the session is linked to the browser session. The
|
or ``None`` if the session is linked to the browser session. The
|
||||||
|
|
@ -362,6 +369,7 @@ class SecureCookieSessionInterface(SessionInterface):
|
||||||
|
|
||||||
httponly = self.get_cookie_httponly(app)
|
httponly = self.get_cookie_httponly(app)
|
||||||
secure = self.get_cookie_secure(app)
|
secure = self.get_cookie_secure(app)
|
||||||
|
samesite = self.get_cookie_samesite(app)
|
||||||
expires = self.get_expiration_time(app, session)
|
expires = self.get_expiration_time(app, session)
|
||||||
val = self.get_signing_serializer(app).dumps(dict(session))
|
val = self.get_signing_serializer(app).dumps(dict(session))
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
|
|
@ -371,5 +379,6 @@ class SecureCookieSessionInterface(SessionInterface):
|
||||||
httponly=httponly,
|
httponly=httponly,
|
||||||
domain=domain,
|
domain=domain,
|
||||||
path=path,
|
path=path,
|
||||||
secure=secure
|
secure=secure,
|
||||||
|
samesite=samesite
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -319,6 +319,7 @@ def test_session_using_session_settings(app, client):
|
||||||
SESSION_COOKIE_DOMAIN='.example.com',
|
SESSION_COOKIE_DOMAIN='.example.com',
|
||||||
SESSION_COOKIE_HTTPONLY=False,
|
SESSION_COOKIE_HTTPONLY=False,
|
||||||
SESSION_COOKIE_SECURE=True,
|
SESSION_COOKIE_SECURE=True,
|
||||||
|
SESSION_COOKIE_SAMESITE='Strict',
|
||||||
SESSION_COOKIE_PATH='/'
|
SESSION_COOKIE_PATH='/'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -333,8 +334,45 @@ def test_session_using_session_settings(app, client):
|
||||||
assert 'path=/' in cookie
|
assert 'path=/' in cookie
|
||||||
assert 'secure' in cookie
|
assert 'secure' in cookie
|
||||||
assert 'httponly' not in cookie
|
assert 'httponly' not in cookie
|
||||||
|
assert 'samesite' in cookie
|
||||||
|
|
||||||
|
|
||||||
|
def test_session_using_samesite_attribute(app, client):
|
||||||
|
app.config.update(
|
||||||
|
SERVER_NAME='www.example.com:8080',
|
||||||
|
APPLICATION_ROOT='/test',
|
||||||
|
SESSION_COOKIE_DOMAIN='.example.com',
|
||||||
|
SESSION_COOKIE_HTTPONLY=False,
|
||||||
|
SESSION_COOKIE_SECURE=True,
|
||||||
|
SESSION_COOKIE_SAMESITE='anyvalue',
|
||||||
|
SESSION_COOKIE_PATH='/'
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
flask.session['testing'] = 42
|
||||||
|
return 'Hello World'
|
||||||
|
|
||||||
|
# assert excption when samesite is not set to 'Strict', 'Lax' or None
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rv = client.get('/', 'http://www.example.com:8080/test/')
|
||||||
|
|
||||||
|
# assert the samesite flag is not set in the cookie, when set to None
|
||||||
|
app.config.update(SESSION_COOKIE_SAMESITE=None)
|
||||||
|
rv = client.get('/', 'http://www.example.com:8080/test/')
|
||||||
|
cookie = rv.headers['set-cookie'].lower()
|
||||||
|
assert 'samesite' not in cookie
|
||||||
|
|
||||||
|
app.config.update(SESSION_COOKIE_SAMESITE='Strict')
|
||||||
|
rv = client.get('/', 'http://www.example.com:8080/test/')
|
||||||
|
cookie = rv.headers['set-cookie'].lower()
|
||||||
|
assert 'samesite=strict' in cookie
|
||||||
|
|
||||||
|
app.config.update(SESSION_COOKIE_SAMESITE='Lax')
|
||||||
|
rv = client.get('/', 'http://www.example.com:8080/test/')
|
||||||
|
cookie = rv.headers['set-cookie'].lower()
|
||||||
|
assert 'samesite=lax' in cookie
|
||||||
|
|
||||||
def test_session_localhost_warning(recwarn, app, client):
|
def test_session_localhost_warning(recwarn, app, client):
|
||||||
app.config.update(
|
app.config.update(
|
||||||
SERVER_NAME='localhost:5000',
|
SERVER_NAME='localhost:5000',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue