diff options
Diffstat (limited to 'lib/python2.7/site-packages/django/contrib/sessions/tests.py')
-rw-r--r-- | lib/python2.7/site-packages/django/contrib/sessions/tests.py | 593 |
1 files changed, 593 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/django/contrib/sessions/tests.py b/lib/python2.7/site-packages/django/contrib/sessions/tests.py new file mode 100644 index 0000000..ff0a70c --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/sessions/tests.py @@ -0,0 +1,593 @@ +import base64 +from datetime import timedelta +import os +import shutil +import string +import tempfile +import warnings + +from django.conf import settings +from django.contrib.sessions.backends.db import SessionStore as DatabaseSession +from django.contrib.sessions.backends.cache import SessionStore as CacheSession +from django.contrib.sessions.backends.cached_db import SessionStore as CacheDBSession +from django.contrib.sessions.backends.file import SessionStore as FileSession +from django.contrib.sessions.backends.signed_cookies import SessionStore as CookieSession +from django.contrib.sessions.models import Session +from django.contrib.sessions.middleware import SessionMiddleware +from django.core.cache import get_cache +from django.core import management +from django.core.exceptions import ImproperlyConfigured +from django.http import HttpResponse +from django.test import TestCase, RequestFactory +from django.test.utils import override_settings, patch_logger +from django.utils import six +from django.utils import timezone +from django.utils import unittest + +from django.contrib.sessions.exceptions import InvalidSessionKey + + +class SessionTestsMixin(object): + # This does not inherit from TestCase to avoid any tests being run with this + # class, which wouldn't work, and to allow different TestCase subclasses to + # be used. + + backend = None # subclasses must specify + + def setUp(self): + self.session = self.backend() + + def tearDown(self): + # NB: be careful to delete any sessions created; stale sessions fill up + # the /tmp (with some backends) and eventually overwhelm it after lots + # of runs (think buildbots) + self.session.delete() + + def test_new_session(self): + self.assertFalse(self.session.modified) + self.assertFalse(self.session.accessed) + + def test_get_empty(self): + self.assertEqual(self.session.get('cat'), None) + + def test_store(self): + self.session['cat'] = "dog" + self.assertTrue(self.session.modified) + self.assertEqual(self.session.pop('cat'), 'dog') + + def test_pop(self): + self.session['some key'] = 'exists' + # Need to reset these to pretend we haven't accessed it: + self.accessed = False + self.modified = False + + self.assertEqual(self.session.pop('some key'), 'exists') + self.assertTrue(self.session.accessed) + self.assertTrue(self.session.modified) + self.assertEqual(self.session.get('some key'), None) + + def test_pop_default(self): + self.assertEqual(self.session.pop('some key', 'does not exist'), + 'does not exist') + self.assertTrue(self.session.accessed) + self.assertFalse(self.session.modified) + + def test_setdefault(self): + self.assertEqual(self.session.setdefault('foo', 'bar'), 'bar') + self.assertEqual(self.session.setdefault('foo', 'baz'), 'bar') + self.assertTrue(self.session.accessed) + self.assertTrue(self.session.modified) + + def test_update(self): + self.session.update({'update key': 1}) + self.assertTrue(self.session.accessed) + self.assertTrue(self.session.modified) + self.assertEqual(self.session.get('update key', None), 1) + + def test_has_key(self): + self.session['some key'] = 1 + self.session.modified = False + self.session.accessed = False + self.assertIn('some key', self.session) + self.assertTrue(self.session.accessed) + self.assertFalse(self.session.modified) + + def test_values(self): + self.assertEqual(list(self.session.values()), []) + self.assertTrue(self.session.accessed) + self.session['some key'] = 1 + self.assertEqual(list(self.session.values()), [1]) + + def test_iterkeys(self): + self.session['x'] = 1 + self.session.modified = False + self.session.accessed = False + i = six.iterkeys(self.session) + self.assertTrue(hasattr(i, '__iter__')) + self.assertTrue(self.session.accessed) + self.assertFalse(self.session.modified) + self.assertEqual(list(i), ['x']) + + def test_itervalues(self): + self.session['x'] = 1 + self.session.modified = False + self.session.accessed = False + i = six.itervalues(self.session) + self.assertTrue(hasattr(i, '__iter__')) + self.assertTrue(self.session.accessed) + self.assertFalse(self.session.modified) + self.assertEqual(list(i), [1]) + + def test_iteritems(self): + self.session['x'] = 1 + self.session.modified = False + self.session.accessed = False + i = six.iteritems(self.session) + self.assertTrue(hasattr(i, '__iter__')) + self.assertTrue(self.session.accessed) + self.assertFalse(self.session.modified) + self.assertEqual(list(i), [('x', 1)]) + + def test_clear(self): + self.session['x'] = 1 + self.session.modified = False + self.session.accessed = False + self.assertEqual(list(self.session.items()), [('x', 1)]) + self.session.clear() + self.assertEqual(list(self.session.items()), []) + self.assertTrue(self.session.accessed) + self.assertTrue(self.session.modified) + + def test_save(self): + if (hasattr(self.session, '_cache') and'DummyCache' in + settings.CACHES[settings.SESSION_CACHE_ALIAS]['BACKEND']): + raise unittest.SkipTest("Session saving tests require a real cache backend") + self.session.save() + self.assertTrue(self.session.exists(self.session.session_key)) + + def test_delete(self): + self.session.save() + self.session.delete(self.session.session_key) + self.assertFalse(self.session.exists(self.session.session_key)) + + def test_flush(self): + self.session['foo'] = 'bar' + self.session.save() + prev_key = self.session.session_key + self.session.flush() + self.assertFalse(self.session.exists(prev_key)) + self.assertNotEqual(self.session.session_key, prev_key) + self.assertTrue(self.session.modified) + self.assertTrue(self.session.accessed) + + def test_cycle(self): + self.session['a'], self.session['b'] = 'c', 'd' + self.session.save() + prev_key = self.session.session_key + prev_data = list(self.session.items()) + self.session.cycle_key() + self.assertNotEqual(self.session.session_key, prev_key) + self.assertEqual(list(self.session.items()), prev_data) + + def test_invalid_key(self): + # Submitting an invalid session key (either by guessing, or if the db has + # removed the key) results in a new key being generated. + try: + session = self.backend('1') + try: + session.save() + except AttributeError: + self.fail("The session object did not save properly. Middleware may be saving cache items without namespaces.") + self.assertNotEqual(session.session_key, '1') + self.assertEqual(session.get('cat'), None) + session.delete() + finally: + # Some backends leave a stale cache entry for the invalid + # session key; make sure that entry is manually deleted + session.delete('1') + + def test_session_key_is_read_only(self): + def set_session_key(session): + session.session_key = session._get_new_session_key() + self.assertRaises(AttributeError, set_session_key, self.session) + + # Custom session expiry + def test_default_expiry(self): + # A normal session has a max age equal to settings + self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE) + + # So does a custom session with an idle expiration time of 0 (but it'll + # expire at browser close) + self.session.set_expiry(0) + self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE) + + def test_custom_expiry_seconds(self): + modification = timezone.now() + + self.session.set_expiry(10) + + date = self.session.get_expiry_date(modification=modification) + self.assertEqual(date, modification + timedelta(seconds=10)) + + age = self.session.get_expiry_age(modification=modification) + self.assertEqual(age, 10) + + def test_custom_expiry_timedelta(self): + modification = timezone.now() + + # Mock timezone.now, because set_expiry calls it on this code path. + original_now = timezone.now + try: + timezone.now = lambda: modification + self.session.set_expiry(timedelta(seconds=10)) + finally: + timezone.now = original_now + + date = self.session.get_expiry_date(modification=modification) + self.assertEqual(date, modification + timedelta(seconds=10)) + + age = self.session.get_expiry_age(modification=modification) + self.assertEqual(age, 10) + + def test_custom_expiry_datetime(self): + modification = timezone.now() + + self.session.set_expiry(modification + timedelta(seconds=10)) + + date = self.session.get_expiry_date(modification=modification) + self.assertEqual(date, modification + timedelta(seconds=10)) + + age = self.session.get_expiry_age(modification=modification) + self.assertEqual(age, 10) + + def test_custom_expiry_reset(self): + self.session.set_expiry(None) + self.session.set_expiry(10) + self.session.set_expiry(None) + self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE) + + def test_get_expire_at_browser_close(self): + # Tests get_expire_at_browser_close with different settings and different + # set_expiry calls + with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=False): + self.session.set_expiry(10) + self.assertFalse(self.session.get_expire_at_browser_close()) + + self.session.set_expiry(0) + self.assertTrue(self.session.get_expire_at_browser_close()) + + self.session.set_expiry(None) + self.assertFalse(self.session.get_expire_at_browser_close()) + + with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=True): + self.session.set_expiry(10) + self.assertFalse(self.session.get_expire_at_browser_close()) + + self.session.set_expiry(0) + self.assertTrue(self.session.get_expire_at_browser_close()) + + self.session.set_expiry(None) + self.assertTrue(self.session.get_expire_at_browser_close()) + + def test_decode(self): + # Ensure we can decode what we encode + data = {'a test key': 'a test value'} + encoded = self.session.encode(data) + self.assertEqual(self.session.decode(encoded), data) + + def test_decode_failure_logged_to_security(self): + bad_encode = base64.b64encode(b'flaskdj:alkdjf') + with patch_logger('django.security.SuspiciousSession', 'warning') as calls: + self.assertEqual({}, self.session.decode(bad_encode)) + # check that the failed decode is logged + self.assertEqual(len(calls), 1) + self.assertTrue('corrupted' in calls[0]) + + + def test_actual_expiry(self): + # this doesn't work with JSONSerializer (serializing timedelta) + with override_settings(SESSION_SERIALIZER='django.contrib.sessions.serializers.PickleSerializer'): + self.session = self.backend() # reinitialize after overriding settings + + # Regression test for #19200 + old_session_key = None + new_session_key = None + try: + self.session['foo'] = 'bar' + self.session.set_expiry(-timedelta(seconds=10)) + self.session.save() + old_session_key = self.session.session_key + # With an expiry date in the past, the session expires instantly. + new_session = self.backend(self.session.session_key) + new_session_key = new_session.session_key + self.assertNotIn('foo', new_session) + finally: + self.session.delete(old_session_key) + self.session.delete(new_session_key) + + +class DatabaseSessionTests(SessionTestsMixin, TestCase): + + backend = DatabaseSession + + def test_session_get_decoded(self): + """ + Test we can use Session.get_decoded to retrieve data stored + in normal way + """ + self.session['x'] = 1 + self.session.save() + + s = Session.objects.get(session_key=self.session.session_key) + + self.assertEqual(s.get_decoded(), {'x': 1}) + + def test_sessionmanager_save(self): + """ + Test SessionManager.save method + """ + # Create a session + self.session['y'] = 1 + self.session.save() + + s = Session.objects.get(session_key=self.session.session_key) + # Change it + Session.objects.save(s.session_key, {'y': 2}, s.expire_date) + # Clear cache, so that it will be retrieved from DB + del self.session._session_cache + self.assertEqual(self.session['y'], 2) + + @override_settings(SESSION_ENGINE="django.contrib.sessions.backends.db") + def test_clearsessions_command(self): + """ + Test clearsessions command for clearing expired sessions. + """ + self.assertEqual(0, Session.objects.count()) + + # One object in the future + self.session['foo'] = 'bar' + self.session.set_expiry(3600) + self.session.save() + + # One object in the past + other_session = self.backend() + other_session['foo'] = 'bar' + other_session.set_expiry(-3600) + other_session.save() + + # Two sessions are in the database before clearsessions... + self.assertEqual(2, Session.objects.count()) + management.call_command('clearsessions') + # ... and one is deleted. + self.assertEqual(1, Session.objects.count()) + + +@override_settings(USE_TZ=True) +class DatabaseSessionWithTimeZoneTests(DatabaseSessionTests): + pass + + +class CacheDBSessionTests(SessionTestsMixin, TestCase): + + backend = CacheDBSession + + @unittest.skipIf('DummyCache' in + settings.CACHES[settings.SESSION_CACHE_ALIAS]['BACKEND'], + "Session saving tests require a real cache backend") + def test_exists_searches_cache_first(self): + self.session.save() + with self.assertNumQueries(0): + self.assertTrue(self.session.exists(self.session.session_key)) + + def test_load_overlong_key(self): + # Some backends might issue a warning + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + self.session._session_key = (string.ascii_letters + string.digits) * 20 + self.assertEqual(self.session.load(), {}) + + +@override_settings(USE_TZ=True) +class CacheDBSessionWithTimeZoneTests(CacheDBSessionTests): + pass + + +# Don't need DB flushing for these tests, so can use unittest.TestCase as base class +class FileSessionTests(SessionTestsMixin, unittest.TestCase): + + backend = FileSession + + def setUp(self): + # Do file session tests in an isolated directory, and kill it after we're done. + self.original_session_file_path = settings.SESSION_FILE_PATH + self.temp_session_store = settings.SESSION_FILE_PATH = tempfile.mkdtemp() + # Reset the file session backend's internal caches + if hasattr(self.backend, '_storage_path'): + del self.backend._storage_path + super(FileSessionTests, self).setUp() + + def tearDown(self): + super(FileSessionTests, self).tearDown() + settings.SESSION_FILE_PATH = self.original_session_file_path + shutil.rmtree(self.temp_session_store) + + @override_settings( + SESSION_FILE_PATH="/if/this/directory/exists/you/have/a/weird/computer") + def test_configuration_check(self): + del self.backend._storage_path + # Make sure the file backend checks for a good storage dir + self.assertRaises(ImproperlyConfigured, self.backend) + + def test_invalid_key_backslash(self): + # This key should be refused and a new session should be created + self.assertTrue(self.backend("a\\b\\c").load()) + + def test_invalid_key_backslash(self): + # Ensure we don't allow directory-traversal. + # This is tested directly on _key_to_file, as load() will swallow + # a SuspiciousOperation in the same way as an IOError - by creating + # a new session, making it unclear whether the slashes were detected. + self.assertRaises(InvalidSessionKey, + self.backend()._key_to_file, "a\\b\\c") + + def test_invalid_key_forwardslash(self): + # Ensure we don't allow directory-traversal + self.assertRaises(InvalidSessionKey, + self.backend()._key_to_file, "a/b/c") + + @override_settings(SESSION_ENGINE="django.contrib.sessions.backends.file") + def test_clearsessions_command(self): + """ + Test clearsessions command for clearing expired sessions. + """ + storage_path = self.backend._get_storage_path() + file_prefix = settings.SESSION_COOKIE_NAME + + def count_sessions(): + return len([session_file for session_file in os.listdir(storage_path) + if session_file.startswith(file_prefix)]) + + self.assertEqual(0, count_sessions()) + + # One object in the future + self.session['foo'] = 'bar' + self.session.set_expiry(3600) + self.session.save() + + # One object in the past + other_session = self.backend() + other_session['foo'] = 'bar' + other_session.set_expiry(-3600) + other_session.save() + + # Two sessions are in the filesystem before clearsessions... + self.assertEqual(2, count_sessions()) + management.call_command('clearsessions') + # ... and one is deleted. + self.assertEqual(1, count_sessions()) + + +class CacheSessionTests(SessionTestsMixin, unittest.TestCase): + + backend = CacheSession + + def test_load_overlong_key(self): + # Some backends might issue a warning + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + self.session._session_key = (string.ascii_letters + string.digits) * 20 + self.assertEqual(self.session.load(), {}) + + def test_default_cache(self): + self.session.save() + self.assertNotEqual(get_cache('default').get(self.session.cache_key), None) + + @override_settings(CACHES={ + 'default': { + 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', + }, + 'sessions': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + }, + }, SESSION_CACHE_ALIAS='sessions') + def test_non_default_cache(self): + # Re-initalize the session backend to make use of overridden settings. + self.session = self.backend() + + self.session.save() + self.assertEqual(get_cache('default').get(self.session.cache_key), None) + self.assertNotEqual(get_cache('sessions').get(self.session.cache_key), None) + + +class SessionMiddlewareTests(unittest.TestCase): + + @override_settings(SESSION_COOKIE_SECURE=True) + def test_secure_session_cookie(self): + request = RequestFactory().get('/') + response = HttpResponse('Session test') + middleware = SessionMiddleware() + + # Simulate a request the modifies the session + middleware.process_request(request) + request.session['hello'] = 'world' + + # Handle the response through the middleware + response = middleware.process_response(request, response) + self.assertTrue( + response.cookies[settings.SESSION_COOKIE_NAME]['secure']) + + @override_settings(SESSION_COOKIE_HTTPONLY=True) + def test_httponly_session_cookie(self): + request = RequestFactory().get('/') + response = HttpResponse('Session test') + middleware = SessionMiddleware() + + # Simulate a request the modifies the session + middleware.process_request(request) + request.session['hello'] = 'world' + + # Handle the response through the middleware + response = middleware.process_response(request, response) + self.assertTrue( + response.cookies[settings.SESSION_COOKIE_NAME]['httponly']) + self.assertIn('httponly', + str(response.cookies[settings.SESSION_COOKIE_NAME])) + + @override_settings(SESSION_COOKIE_HTTPONLY=False) + def test_no_httponly_session_cookie(self): + request = RequestFactory().get('/') + response = HttpResponse('Session test') + middleware = SessionMiddleware() + + # Simulate a request the modifies the session + middleware.process_request(request) + request.session['hello'] = 'world' + + # Handle the response through the middleware + response = middleware.process_response(request, response) + self.assertFalse(response.cookies[settings.SESSION_COOKIE_NAME]['httponly']) + + self.assertNotIn('httponly', + str(response.cookies[settings.SESSION_COOKIE_NAME])) + + def test_session_save_on_500(self): + request = RequestFactory().get('/') + response = HttpResponse('Horrible error') + response.status_code = 500 + middleware = SessionMiddleware() + + # Simulate a request the modifies the session + middleware.process_request(request) + request.session['hello'] = 'world' + + # Handle the response through the middleware + response = middleware.process_response(request, response) + + # Check that the value wasn't saved above. + self.assertNotIn('hello', request.session.load()) + + +class CookieSessionTests(SessionTestsMixin, TestCase): + + backend = CookieSession + + def test_save(self): + """ + This test tested exists() in the other session backends, but that + doesn't make sense for us. + """ + pass + + def test_cycle(self): + """ + This test tested cycle_key() which would create a new session + key for the same session data. But we can't invalidate previously + signed cookies (other than letting them expire naturally) so + testing for this behavior is meaningless. + """ + pass + + @unittest.expectedFailure + def test_actual_expiry(self): + # The cookie backend doesn't handle non-default expiry dates, see #19201 + super(CookieSessionTests, self).test_actual_expiry() |