From fcefada2d7e20e16315818e62fc65c5cef51ba09 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Wed, 22 Jan 2014 15:28:53 -0500 Subject: [PATCH] add middleware to be able to expire inactive sessions after N seconds configure middleware add test for session inactive timeouts add Studio inactive session timeout test change login method used add create_test_account to test make sure the expected redirect URL is right fix indenting problem fix doc string since we moved from minutes to seconds use utility methods rather than calling another test to set up and activate an account clean up code violations respond to PR feedback use optional params to make code cleaner pylint fix on test files --- cms/djangoapps/contentstore/tests/tests.py | 29 ++++++++++ cms/envs/common.py | 3 ++ .../session_inactivity_timeout/__init__.py | 0 .../session_inactivity_timeout/middleware.py | 53 +++++++++++++++++++ .../courseware/tests/test_navigation.py | 28 ++++++++++ lms/envs/common.py | 3 ++ 6 files changed, 116 insertions(+) create mode 100644 common/djangoapps/session_inactivity_timeout/__init__.py create mode 100644 common/djangoapps/session_inactivity_timeout/middleware.py diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py index 7ac99e9a64..691239903e 100644 --- a/cms/djangoapps/contentstore/tests/tests.py +++ b/cms/djangoapps/contentstore/tests/tests.py @@ -1,6 +1,12 @@ +""" +This test file will test registration, login, activation, and session activity timeouts +""" +import time + from django.test.utils import override_settings from django.core.cache import cache from django.core.urlresolvers import reverse +from django.conf import settings from contentstore.tests.utils import parse_json, user, registration, AjaxEnabledTestClient from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase @@ -188,6 +194,29 @@ class AuthTestCase(ContentStoreTestCase): # Logged in should work. + @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1) + def test_inactive_session_timeout(self): + """ + Verify that an inactive session times out and redirects to the + login page + """ + self.create_account(self.username, self.email, self.pw) + self.activate_user(self.email) + + self.login(self.email, self.pw) + + # make sure we can access courseware immediately + resp = self.client.get_html('/course') + self.assertEquals(resp.status_code, 200) + + # then wait a bit and see if we get timed out + time.sleep(2) + + resp = self.client.get_html('/course') + + # re-request, and we should get a redirect to login page + self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL + '?next=/course') + class ForumTestCase(CourseTestCase): def setUp(self): diff --git a/cms/envs/common.py b/cms/envs/common.py index 62e52d2eb5..c96b171959 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -174,6 +174,9 @@ MIDDLEWARE_CLASSES = ( # catches any uncaught RateLimitExceptions and returns a 403 instead of a 500 'ratelimitbackend.middleware.RateLimitMiddleware', + + # for expiring inactive sessions + 'session_inactivity_timeout.middleware.SessionInactivityTimeout', ) ############# XBlock Configuration ########## diff --git a/common/djangoapps/session_inactivity_timeout/__init__.py b/common/djangoapps/session_inactivity_timeout/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/djangoapps/session_inactivity_timeout/middleware.py b/common/djangoapps/session_inactivity_timeout/middleware.py new file mode 100644 index 0000000000..1473d3cef8 --- /dev/null +++ b/common/djangoapps/session_inactivity_timeout/middleware.py @@ -0,0 +1,53 @@ +""" +Middleware to auto-expire inactive sessions after N seconds, which is configurable in +settings. + +To enable this feature, set in a settings.py: + + SESSION_INACTIVITY_TIMEOUT_IN_SECS = 300 + +This was taken from StackOverflow (http://stackoverflow.com/questions/14830669/how-to-expire-django-session-in-5minutes) +""" +from datetime import datetime, timedelta +from django.conf import settings +from django.contrib import auth + +LAST_TOUCH_KEYNAME = 'SessionInactivityTimeout:last_touch' + + +class SessionInactivityTimeout(object): + """ + Middleware class to keep track of activity on a given session + """ + def process_request(self, request): + """ + Standard entry point for processing requests in Django + """ + if not hasattr(request, "user") or not request.user.is_authenticated(): + #Can't log out if not logged in + return + + timeout_in_seconds = getattr(settings, "SESSION_INACTIVITY_TIMEOUT_IN_SECONDS", None) + + # Do we have this feature enabled? + if timeout_in_seconds: + # what time is it now? + utc_now = datetime.utcnow() + + # Get the last time user made a request to server, which is stored in session data + last_touch = request.session.get(LAST_TOUCH_KEYNAME) + + # have we stored a 'last visited' in session? NOTE: first time access after login + # this key will not be present in the session data + if last_touch: + # compute the delta since last time user came to the server + time_since_last_activity = utc_now - last_touch + + # did we exceed the timeout limit? + if time_since_last_activity > timedelta(seconds=timeout_in_seconds): + # yes? Then log the user out + del request.session[LAST_TOUCH_KEYNAME] + auth.logout(request) + return + + request.session[LAST_TOUCH_KEYNAME] = utc_now diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py index 2b416b16de..d9aaa8d9aa 100644 --- a/lms/djangoapps/courseware/tests/test_navigation.py +++ b/lms/djangoapps/courseware/tests/test_navigation.py @@ -1,3 +1,9 @@ +""" +This test file will run through some LMS test scenarios regarding access and navigation of the LMS +""" +import time +from django.conf import settings + from django.core.urlresolvers import reverse from django.test.utils import override_settings @@ -37,6 +43,28 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): self.create_account(username, email, password) self.activate_user(email) + @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1) + def test_inactive_session_timeout(self): + """ + Verify that an inactive session times out and redirects to the + login page + """ + email, password = self.STUDENT_INFO[0] + self.login(email, password) + + # make sure we can access courseware immediately + resp = self.client.get(reverse('dashboard')) + self.assertEquals(resp.status_code, 200) + + # then wait a bit and see if we get timed out + time.sleep(2) + + resp = self.client.get(reverse('dashboard')) + + # re-request, and we should get a redirect to login page + self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL + '?next=' + reverse('dashboard')) + + def test_redirects_first_time(self): """ Verify that the first time we click on the courseware tab we are diff --git a/lms/envs/common.py b/lms/envs/common.py index 44ccb1771e..36d7980684 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -654,6 +654,9 @@ MIDDLEWARE_CLASSES = ( # For A/B testing 'waffle.middleware.WaffleMiddleware', + + # for expiring inactive sessions + 'session_inactivity_timeout.middleware.SessionInactivityTimeout', ) ############################### Pipeline #######################################