mirror of https://github.com/pallets/flask.git
Added SESSION_REFRESH_EACH_REQUEST config option.
This also changes how sessions are being refreshed. With the new behavior set-cookie is only emitted if the session is modified or if the session is permanent. Permanent sessions can be set to not refresh automatically through the SESSION_REFRESH_EACH_REQUEST config key. This fixes #798.
This commit is contained in:
parent
1a66a7e110
commit
d1d835c023
7
CHANGES
7
CHANGES
|
@ -8,6 +8,13 @@ Version 1.0
|
||||||
|
|
||||||
(release date to be announced, codename to be selected)
|
(release date to be announced, codename to be selected)
|
||||||
|
|
||||||
|
- Added ``SESSION_REFRESH_EACH_REQUEST`` config key that controls the
|
||||||
|
set-cookie behavior. If set to `True` a permanent session will be
|
||||||
|
refreshed each request and get their lifetime extended, if set to
|
||||||
|
`False` it will only be modified if the session actually modifies.
|
||||||
|
Non permanent sessions are not affected by this and will always
|
||||||
|
expire if the browser window closes.
|
||||||
|
|
||||||
Version 0.10.2
|
Version 0.10.2
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,15 @@ The following configuration values are used internally by Flask:
|
||||||
:class:`datetime.timedelta` object.
|
:class:`datetime.timedelta` object.
|
||||||
Starting with Flask 0.8 this can also be
|
Starting with Flask 0.8 this can also be
|
||||||
an integer representing seconds.
|
an integer representing seconds.
|
||||||
|
``SESSION_REFRESH_EACH_REQUEST`` this flag controls how permanent
|
||||||
|
sessions are refresh. If set to `True`
|
||||||
|
(which is the default) then the cookie
|
||||||
|
is refreshed each request which
|
||||||
|
automatically bumps the lifetime. If
|
||||||
|
set to `False` a `set-cookie` header is
|
||||||
|
only sent if the session is modified.
|
||||||
|
Non permanent sessions are not affected
|
||||||
|
by this.
|
||||||
``USE_X_SENDFILE`` enable/disable x-sendfile
|
``USE_X_SENDFILE`` enable/disable x-sendfile
|
||||||
``LOGGER_NAME`` the name of the logger
|
``LOGGER_NAME`` the name of the logger
|
||||||
``SERVER_NAME`` the name and port number of the server.
|
``SERVER_NAME`` the name and port number of the server.
|
||||||
|
@ -210,6 +219,9 @@ The following configuration values are used internally by Flask:
|
||||||
.. versionadded:: 0.10
|
.. versionadded:: 0.10
|
||||||
``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_PRETTYPRINT_REGULAR``
|
``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_PRETTYPRINT_REGULAR``
|
||||||
|
|
||||||
|
.. versionadded:: 1.0
|
||||||
|
``SESSION_REFRESH_EACH_REQUEST``
|
||||||
|
|
||||||
Configuring from Files
|
Configuring from Files
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -285,6 +285,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_REFRESH_EACH_REQUEST': True,
|
||||||
'MAX_CONTENT_LENGTH': None,
|
'MAX_CONTENT_LENGTH': None,
|
||||||
'SEND_FILE_MAX_AGE_DEFAULT': 12 * 60 * 60, # 12 hours
|
'SEND_FILE_MAX_AGE_DEFAULT': 12 * 60 * 60, # 12 hours
|
||||||
'TRAP_BAD_REQUEST_ERRORS': False,
|
'TRAP_BAD_REQUEST_ERRORS': False,
|
||||||
|
|
|
@ -252,6 +252,24 @@ class SessionInterface(object):
|
||||||
if session.permanent:
|
if session.permanent:
|
||||||
return datetime.utcnow() + app.permanent_session_lifetime
|
return datetime.utcnow() + app.permanent_session_lifetime
|
||||||
|
|
||||||
|
def should_set_cookie(self, app, session):
|
||||||
|
"""Indicates weather a cookie should be set now or not. This is
|
||||||
|
used by session backends to figure out if they should emit a
|
||||||
|
set-cookie header or not. The default behavior is controlled by
|
||||||
|
the ``SESSION_REFRESH_EACH_REQUEST`` config variable. If
|
||||||
|
it's set to `False` then a cookie is only set if the session is
|
||||||
|
modified, if set to `True` it's always set if the session is
|
||||||
|
permanent.
|
||||||
|
|
||||||
|
This check is usually skipped if sessions get deleted.
|
||||||
|
|
||||||
|
.. versionadded:: 1.0
|
||||||
|
"""
|
||||||
|
if session.modified:
|
||||||
|
return True
|
||||||
|
save_each = app.config['SESSION_REFRESH_EACH_REQUEST']
|
||||||
|
return save_each and session.permanent
|
||||||
|
|
||||||
def open_session(self, app, request):
|
def open_session(self, app, request):
|
||||||
"""This method has to be implemented and must either return `None`
|
"""This method has to be implemented and must either return `None`
|
||||||
in case the loading failed because of a configuration error or an
|
in case the loading failed because of a configuration error or an
|
||||||
|
@ -315,11 +333,26 @@ class SecureCookieSessionInterface(SessionInterface):
|
||||||
def save_session(self, app, session, response):
|
def save_session(self, app, session, response):
|
||||||
domain = self.get_cookie_domain(app)
|
domain = self.get_cookie_domain(app)
|
||||||
path = self.get_cookie_path(app)
|
path = self.get_cookie_path(app)
|
||||||
|
|
||||||
|
# Delete case. If there is no session we bail early.
|
||||||
|
# If the session was modified to be empty we remove the
|
||||||
|
# whole cookie.
|
||||||
if not session:
|
if not session:
|
||||||
if session.modified:
|
if session.modified:
|
||||||
response.delete_cookie(app.session_cookie_name,
|
response.delete_cookie(app.session_cookie_name,
|
||||||
domain=domain, path=path)
|
domain=domain, path=path)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Modification case. There are upsides and downsides to
|
||||||
|
# emitting a set-cookie header each request. The behavior
|
||||||
|
# is controlled by the :meth:`should_set_cookie` method
|
||||||
|
# which performs a quick check to figure out if the cookie
|
||||||
|
# should be set or not. This is controlled by the
|
||||||
|
# SESSION_REFRESH_EACH_REQUEST config flag as well as
|
||||||
|
# the permanent flag on the session itself.
|
||||||
|
if not self.should_set_cookie(app, session):
|
||||||
|
return
|
||||||
|
|
||||||
httponly = self.get_cookie_httponly(app)
|
httponly = self.get_cookie_httponly(app)
|
||||||
secure = self.get_cookie_secure(app)
|
secure = self.get_cookie_secure(app)
|
||||||
expires = self.get_expiration_time(app, session)
|
expires = self.get_expiration_time(app, session)
|
||||||
|
|
|
@ -345,6 +345,49 @@ class BasicFunctionalityTestCase(FlaskTestCase):
|
||||||
self.assert_equal(type(rv['b']), bytes)
|
self.assert_equal(type(rv['b']), bytes)
|
||||||
self.assert_equal(rv['t'], (1, 2, 3))
|
self.assert_equal(rv['t'], (1, 2, 3))
|
||||||
|
|
||||||
|
def test_session_cookie_setting(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.testing = True
|
||||||
|
app.secret_key = 'dev key'
|
||||||
|
is_permanent = True
|
||||||
|
|
||||||
|
@app.route('/bump')
|
||||||
|
def bump():
|
||||||
|
rv = flask.session['foo'] = flask.session.get('foo', 0) + 1
|
||||||
|
flask.session.permanent = is_permanent
|
||||||
|
return str(rv)
|
||||||
|
|
||||||
|
@app.route('/read')
|
||||||
|
def read():
|
||||||
|
return str(flask.session.get('foo', 0))
|
||||||
|
|
||||||
|
def run_test(expect_header):
|
||||||
|
with app.test_client() as c:
|
||||||
|
self.assert_equal(c.get('/bump').data, '1')
|
||||||
|
self.assert_equal(c.get('/bump').data, '2')
|
||||||
|
self.assert_equal(c.get('/bump').data, '3')
|
||||||
|
|
||||||
|
rv = c.get('/read')
|
||||||
|
set_cookie = rv.headers.get('set-cookie')
|
||||||
|
self.assert_equal(set_cookie is not None, expect_header)
|
||||||
|
self.assert_equal(rv.data, '3')
|
||||||
|
|
||||||
|
is_permanent = True
|
||||||
|
app.config['SESSION_REFRESH_EACH_REQUEST'] = True
|
||||||
|
run_test(expect_header=True)
|
||||||
|
|
||||||
|
is_permanent = True
|
||||||
|
app.config['SESSION_REFRESH_EACH_REQUEST'] = False
|
||||||
|
run_test(expect_header=False)
|
||||||
|
|
||||||
|
is_permanent = False
|
||||||
|
app.config['SESSION_REFRESH_EACH_REQUEST'] = True
|
||||||
|
run_test(expect_header=False)
|
||||||
|
|
||||||
|
is_permanent = False
|
||||||
|
app.config['SESSION_REFRESH_EACH_REQUEST'] = False
|
||||||
|
run_test(expect_header=False)
|
||||||
|
|
||||||
def test_flashes(self):
|
def test_flashes(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
app.secret_key = 'testkey'
|
app.secret_key = 'testkey'
|
||||||
|
|
Loading…
Reference in New Issue