Fixed #19200 -- Session expiry with cached_db

Also did a little bit of cleanup.
This commit is contained in:
Aymeric Augustin 2012-10-27 19:18:03 +02:00
parent 83ba0a9d4b
commit 04b00b668d
5 changed files with 52 additions and 15 deletions

View File

@ -170,8 +170,12 @@ class SessionBase(object):
_session = property(_get_session) _session = property(_get_session)
def get_expiry_age(self): def get_expiry_age(self, expiry=None):
"""Get the number of seconds until the session expires.""" """Get the number of seconds until the session expires.
expiry is an optional parameter specifying the datetime of expiry.
"""
if expiry is None:
expiry = self.get('_session_expiry') expiry = self.get('_session_expiry')
if not expiry: # Checks both None and 0 cases if not expiry: # Checks both None and 0 cases
return settings.SESSION_COOKIE_AGE return settings.SESSION_COOKIE_AGE

View File

@ -2,9 +2,10 @@
Cached, database-backed sessions. Cached, database-backed sessions.
""" """
from django.conf import settings
from django.contrib.sessions.backends.db import SessionStore as DBStore from django.contrib.sessions.backends.db import SessionStore as DBStore
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import SuspiciousOperation
from django.utils import timezone
KEY_PREFIX = "django.contrib.sessions.cached_db" KEY_PREFIX = "django.contrib.sessions.cached_db"
@ -28,9 +29,21 @@ class SessionStore(DBStore):
# Some backends (e.g. memcache) raise an exception on invalid # Some backends (e.g. memcache) raise an exception on invalid
# cache keys. If this happens, reset the session. See #17810. # cache keys. If this happens, reset the session. See #17810.
data = None data = None
if data is None: if data is None:
data = super(SessionStore, self).load() # Duplicate DBStore.load, because we need to keep track
cache.set(self.cache_key, data, settings.SESSION_COOKIE_AGE) # of the expiry date to set it properly in the cache.
try:
s = Session.objects.get(
session_key=self.session_key,
expire_date__gt=timezone.now()
)
data = self.decode(s.session_data)
cache.set(self.cache_key, data,
self.get_expiry_age(s.expire_date))
except (Session.DoesNotExist, SuspiciousOperation):
self.create()
data = {}
return data return data
def exists(self, session_key): def exists(self, session_key):
@ -40,7 +53,7 @@ class SessionStore(DBStore):
def save(self, must_create=False): def save(self, must_create=False):
super(SessionStore, self).save(must_create) super(SessionStore, self).save(must_create)
cache.set(self.cache_key, self._session, settings.SESSION_COOKIE_AGE) cache.set(self.cache_key, self._session, self.get_expiry_age())
def delete(self, session_key=None): def delete(self, session_key=None):
super(SessionStore, self).delete(session_key) super(SessionStore, self).delete(session_key)
@ -58,3 +71,7 @@ class SessionStore(DBStore):
self.clear() self.clear()
self.delete(self.session_key) self.delete(self.session_key)
self.create() self.create()
# At bottom to avoid circular import
from django.contrib.sessions.models import Session

View File

@ -32,6 +32,7 @@ class SessionStore(SessionBase):
try: try:
return signing.loads(self.session_key, return signing.loads(self.session_key,
serializer=PickleSerializer, serializer=PickleSerializer,
# This doesn't handle non-default expiry dates, see #19201
max_age=settings.SESSION_COOKIE_AGE, max_age=settings.SESSION_COOKIE_AGE,
salt='django.contrib.sessions.backends.signed_cookies') salt='django.contrib.sessions.backends.signed_cookies')
except (signing.BadSignature, ValueError): except (signing.BadSignature, ValueError):

View File

@ -83,7 +83,7 @@ class SessionTestsMixin(object):
self.session['some key'] = 1 self.session['some key'] = 1
self.session.modified = False self.session.modified = False
self.session.accessed = False self.session.accessed = False
self.assertTrue('some key' in self.session) self.assertIn('some key', self.session)
self.assertTrue(self.session.accessed) self.assertTrue(self.session.accessed)
self.assertFalse(self.session.modified) self.assertFalse(self.session.modified)
@ -200,28 +200,28 @@ class SessionTestsMixin(object):
# Using seconds # Using seconds
self.session.set_expiry(10) self.session.set_expiry(10)
delta = self.session.get_expiry_date() - timezone.now() delta = self.session.get_expiry_date() - timezone.now()
self.assertTrue(delta.seconds in (9, 10)) self.assertIn(delta.seconds, (9, 10))
age = self.session.get_expiry_age() age = self.session.get_expiry_age()
self.assertTrue(age in (9, 10)) self.assertIn(age, (9, 10))
def test_custom_expiry_timedelta(self): def test_custom_expiry_timedelta(self):
# Using timedelta # Using timedelta
self.session.set_expiry(timedelta(seconds=10)) self.session.set_expiry(timedelta(seconds=10))
delta = self.session.get_expiry_date() - timezone.now() delta = self.session.get_expiry_date() - timezone.now()
self.assertTrue(delta.seconds in (9, 10)) self.assertIn(delta.seconds, (9, 10))
age = self.session.get_expiry_age() age = self.session.get_expiry_age()
self.assertTrue(age in (9, 10)) self.assertIn(age, (9, 10))
def test_custom_expiry_datetime(self): def test_custom_expiry_datetime(self):
# Using fixed datetime # Using fixed datetime
self.session.set_expiry(timezone.now() + timedelta(seconds=10)) self.session.set_expiry(timezone.now() + timedelta(seconds=10))
delta = self.session.get_expiry_date() - timezone.now() delta = self.session.get_expiry_date() - timezone.now()
self.assertTrue(delta.seconds in (9, 10)) self.assertIn(delta.seconds, (9, 10))
age = self.session.get_expiry_age() age = self.session.get_expiry_age()
self.assertTrue(age in (9, 10)) self.assertIn(age, (9, 10))
def test_custom_expiry_reset(self): def test_custom_expiry_reset(self):
self.session.set_expiry(None) self.session.set_expiry(None)
@ -258,6 +258,21 @@ class SessionTestsMixin(object):
encoded = self.session.encode(data) encoded = self.session.encode(data)
self.assertEqual(self.session.decode(encoded), data) self.assertEqual(self.session.decode(encoded), data)
def test_actual_expiry(self):
# 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.create()
# With an expiry date in the past, the session expires instantly.
new_session = self.backend(self.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): class DatabaseSessionTests(SessionTestsMixin, TestCase):