Change session timeout to an idle timeout value
Add a new config SESSION_REFRESH (default True) which turns SESSION_TIMEOUT into an idle timeout rather than a hard timeout. The existing hard timeout is awful UX, and while SESSION_TIMEOUT could be set to a higher value, it still makes for a somewhat unpleasant experience. Co-Authored-By: Akihiro Motoki <amotoki@gmail.com> Change-Id: Icc6942e62c4e8d2fac57988b0a2233a8073b1944
This commit is contained in:
parent
06ab7a5047
commit
dc0ffaf2d8
@ -798,6 +798,16 @@ in `AVAILABLE_THEMES`_, but a brander may wish to simply inherit from an
|
||||
existing theme and not allow that parent theme to be selected by the user.
|
||||
``SELECTABLE_THEMES`` takes the exact same format as ``AVAILABLE_THEMES``.
|
||||
|
||||
SESSION_REFRESH
|
||||
---------------
|
||||
|
||||
.. versionadded:: 15.0.0(Stein)
|
||||
|
||||
Default: ``True``
|
||||
|
||||
Control whether the SESSION_TIMEOUT period is refreshed due to activity. If
|
||||
False, SESSION_TIMEOUT acts as a hard limit.
|
||||
|
||||
SESSION_TIMEOUT
|
||||
---------------
|
||||
|
||||
@ -805,9 +815,14 @@ SESSION_TIMEOUT
|
||||
|
||||
Default: ``"3600"``
|
||||
|
||||
This SESSION_TIMEOUT is a method to supercede the token timeout with a shorter
|
||||
horizon session timeout (in seconds). So if your token expires in 60 minutes,
|
||||
a value of 1800 will log users out after 30 minutes.
|
||||
This SESSION_TIMEOUT is a method to supercede the token timeout with a
|
||||
shorter horizon session timeout (in seconds). If SESSION_REFRESH is True (the
|
||||
default) SESSION_TIMEOUT acts like an idle timeout rather than being a hard
|
||||
limit, but will never exceed the token expiry. If your token expires in 60
|
||||
minutes, a value of 1800 will log users out after 30 minutes of inactivity,
|
||||
or 60 minutes with activity. Setting SESSION_REFRESH to False will make
|
||||
SESSION_TIMEOUT act like a hard limit on session times.
|
||||
|
||||
|
||||
MEMOIZED_MAX_SIZE_DEFAULT
|
||||
-------------------------
|
||||
|
@ -19,9 +19,12 @@
|
||||
Middleware provided and used by Horizon.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
import pytz
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
@ -65,6 +68,15 @@ class HorizonMiddleware(object):
|
||||
# to avoid creating too many sessions
|
||||
return None
|
||||
|
||||
# Since we know the user is present and authenticated, lets refresh the
|
||||
# session expiry if configured to do so.
|
||||
if getattr(settings, "SESSION_REFRESH", True):
|
||||
timeout = getattr(settings, "SESSION_TIMEOUT", 3600)
|
||||
token_life = request.user.token.expires - datetime.datetime.now(
|
||||
pytz.utc)
|
||||
session_time = min(timeout, int(token_life.total_seconds()))
|
||||
request.session.set_expiry(session_time)
|
||||
|
||||
if request.is_ajax():
|
||||
# if the request is Ajax we do not want to proceed, as clients can
|
||||
# 1) create pages with constant polling, which can create race
|
||||
|
@ -13,11 +13,15 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
|
||||
import mock
|
||||
import pytz
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponseRedirect
|
||||
from django import test as django_test
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import timezone
|
||||
|
||||
from horizon import exceptions
|
||||
@ -65,11 +69,13 @@ class MiddlewareTests(django_test.TestCase):
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(url, resp['X-Horizon-Location'])
|
||||
|
||||
@override_settings(SESSION_REFRESH=False)
|
||||
def test_timezone_awareness(self):
|
||||
url = settings.LOGIN_REDIRECT_URL
|
||||
mw = middleware.HorizonMiddleware(self.get_response)
|
||||
|
||||
request = self.factory.get(url)
|
||||
|
||||
request.session['django_timezone'] = 'America/Chicago'
|
||||
mw._process_request(request)
|
||||
self.assertEqual(
|
||||
@ -80,3 +86,67 @@ class MiddlewareTests(django_test.TestCase):
|
||||
request.session['django_timezone'] = 'UTC'
|
||||
mw._process_request(request)
|
||||
self.assertEqual(timezone.get_current_timezone_name(), 'UTC')
|
||||
|
||||
@override_settings(SESSION_TIMEOUT=600,
|
||||
SESSION_REFRESH=True)
|
||||
def test_refresh_session_expiry_enough_token_life(self):
|
||||
url = settings.LOGIN_REDIRECT_URL
|
||||
mw = middleware.HorizonMiddleware(self.get_response)
|
||||
|
||||
request = self.factory.get(url)
|
||||
|
||||
now = datetime.datetime.now(pytz.utc)
|
||||
token_expiry = now + datetime.timedelta(seconds=1800)
|
||||
request.user.token = mock.Mock(expires=token_expiry)
|
||||
session_expiry_before = now + datetime.timedelta(seconds=300)
|
||||
request.session.set_expiry(session_expiry_before)
|
||||
|
||||
mw._process_request(request)
|
||||
|
||||
session_expiry_after = request.session.get_expiry_date()
|
||||
# Check if session_expiry has been updated.
|
||||
self.assertGreater(session_expiry_after, session_expiry_before)
|
||||
# Check session_expiry is before token expiry
|
||||
self.assertLess(session_expiry_after, token_expiry)
|
||||
|
||||
@override_settings(SESSION_TIMEOUT=600,
|
||||
SESSION_REFRESH=True)
|
||||
def test_refresh_session_expiry_near_token_expiry(self):
|
||||
url = settings.LOGIN_REDIRECT_URL
|
||||
mw = middleware.HorizonMiddleware(self.get_response)
|
||||
|
||||
request = self.factory.get(url)
|
||||
|
||||
now = datetime.datetime.now(pytz.utc)
|
||||
token_expiry = now + datetime.timedelta(seconds=10)
|
||||
request.user.token = mock.Mock(expires=token_expiry)
|
||||
|
||||
mw._process_request(request)
|
||||
|
||||
session_expiry_after = request.session.get_expiry_date()
|
||||
# Check if session_expiry_after is around token_expiry.
|
||||
# We set some margin to avoid accidental test failure.
|
||||
self.assertGreater(session_expiry_after,
|
||||
token_expiry - datetime.timedelta(seconds=3))
|
||||
self.assertLess(session_expiry_after,
|
||||
token_expiry + datetime.timedelta(seconds=3))
|
||||
|
||||
@override_settings(SESSION_TIMEOUT=600,
|
||||
SESSION_REFRESH=False)
|
||||
def test_no_refresh_session_expiry(self):
|
||||
url = settings.LOGIN_REDIRECT_URL
|
||||
mw = middleware.HorizonMiddleware(self.get_response)
|
||||
|
||||
request = self.factory.get(url)
|
||||
|
||||
now = datetime.datetime.now(pytz.utc)
|
||||
token_expiry = now + datetime.timedelta(seconds=1800)
|
||||
request.user.token = mock.Mock(expires=token_expiry)
|
||||
session_expiry_before = now + datetime.timedelta(seconds=300)
|
||||
request.session.set_expiry(session_expiry_before)
|
||||
|
||||
mw._process_request(request)
|
||||
|
||||
session_expiry_after = request.session.get_expiry_date()
|
||||
# Check if session_expiry has been updated.
|
||||
self.assertEqual(session_expiry_after, session_expiry_before)
|
||||
|
@ -18,6 +18,7 @@ import copy
|
||||
|
||||
from django.conf import settings
|
||||
from django import http
|
||||
from django.test.utils import override_settings
|
||||
|
||||
import six
|
||||
|
||||
@ -348,6 +349,7 @@ class TabExceptionTests(test.TestCase):
|
||||
super(TabExceptionTests, self).tearDown()
|
||||
TabWithTableView.tab_group_class.tabs = self._original_tabs
|
||||
|
||||
@override_settings(SESSION_REFRESH=False)
|
||||
def test_tab_view_exception(self):
|
||||
TabWithTableView.tab_group_class.tabs.append(RecoverableErrorTab)
|
||||
view = TabWithTableView.as_view()
|
||||
@ -355,6 +357,7 @@ class TabExceptionTests(test.TestCase):
|
||||
res = view(req)
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@override_settings(SESSION_REFRESH=False)
|
||||
def test_tab_302_exception(self):
|
||||
TabWithTableView.tab_group_class.tabs.append(RedirectExceptionTab)
|
||||
view = TabWithTableView.as_view()
|
||||
|
@ -25,6 +25,7 @@ from six import moves
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test.utils import override_settings
|
||||
from django import urls
|
||||
|
||||
import horizon
|
||||
@ -268,6 +269,7 @@ class HorizonTests(BaseHorizonTests):
|
||||
self.assertEqual(redirect_url,
|
||||
resp["X-Horizon-Location"])
|
||||
|
||||
@override_settings(SESSION_REFRESH=False)
|
||||
def test_required_permissions(self):
|
||||
dash = horizon.get_dashboard("cats")
|
||||
panel = dash.get_panel('tigers')
|
||||
@ -427,6 +429,7 @@ class CustomPermissionsTests(BaseHorizonTests):
|
||||
# refresh config
|
||||
conf.HORIZON_CONFIG._setup()
|
||||
|
||||
@override_settings(SESSION_REFRESH=False)
|
||||
def test_customized_permissions(self):
|
||||
dogs = horizon.get_dashboard("dogs")
|
||||
panel = dogs.get_panel('puppies')
|
||||
|
@ -203,9 +203,17 @@ SESSION_COOKIE_HTTPONLY = True
|
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
||||
SESSION_COOKIE_SECURE = False
|
||||
|
||||
# SESSION_TIMEOUT is a method to supersede the token timeout with a shorter
|
||||
# horizon session timeout (in seconds). So if your token expires in 60
|
||||
# minutes, a value of 1800 will log users out after 30 minutes
|
||||
# Control whether the SESSION_TIMEOUT period is refreshed due to activity. If
|
||||
# False, SESSION_TIMEOUT acts as a hard limit.
|
||||
SESSION_REFRESH = True
|
||||
|
||||
# This SESSION_TIMEOUT is a method to supercede the token timeout with a
|
||||
# shorter horizon session timeout (in seconds). If SESSION_REFRESH is True (the
|
||||
# default) SESSION_TIMEOUT acts like an idle timeout rather than being a hard
|
||||
# limit, but will never exceed the token expiry. If your token expires in 60
|
||||
# minutes, a value of 1800 will log users out after 30 minutes of inactivity,
|
||||
# or 60 minutes with activity. Setting SESSION_REFRESH to False will make
|
||||
# SESSION_TIMEOUT act like a hard limit on session times.
|
||||
SESSION_TIMEOUT = 3600
|
||||
|
||||
# When using cookie-based sessions, log error when the session cookie exceeds
|
||||
|
@ -0,0 +1,11 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
New setting ``SESSION_REFRESH`` (defaults to ``True``) that allows the user
|
||||
session expiry to be refreshed for every request until the token itself
|
||||
expires. ``SESSION_TIMEOUT`` acts as an idle timeout value now.
|
||||
upgrade:
|
||||
- |
|
||||
``SESSION_TIMEOUT`` now by default acts as an idle timeout rather than a
|
||||
hard timeout limit. If you wish to retain the old hard timeout
|
||||
functionality set ``SESSION_REFRESH`` to ``False``.
|
Loading…
Reference in New Issue
Block a user