Fixed #17810. Catch session key errors.

Catches memcached session key errors related to overly long session keys.
This is a long-standing bug, but severity was exacerbated by the addition
of cookie-backed session storage, which generates long session values. If
an installation switched from cookie-backed session store to memcached,
users would not be able to log in because of the server error from overly
long memcached keys.



git-svn-id: http://code.djangoproject.com/svn/django/trunk@17795 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Paul McMillan 2012-03-23 05:31:11 +00:00
parent 38061221c3
commit 2ca9801956
3 changed files with 39 additions and 7 deletions

View File

@ -16,7 +16,13 @@ class SessionStore(SessionBase):
return KEY_PREFIX + self._get_or_create_session_key() return KEY_PREFIX + self._get_or_create_session_key()
def load(self): def load(self):
session_data = self._cache.get(self.cache_key) try:
session_data = self._cache.get(self.cache_key, None)
except Exception as e:
e_type = str(type(e))
if e_type != "<class 'memcache.MemcachedKeyLengthError'>":
raise e
session_data = None
if session_data is not None: if session_data is not None:
return session_data return session_data
self.create() self.create()

View File

@ -21,7 +21,13 @@ class SessionStore(DBStore):
return KEY_PREFIX + self._get_or_create_session_key() return KEY_PREFIX + self._get_or_create_session_key()
def load(self): def load(self):
data = cache.get(self.cache_key, None) try:
data = cache.get(self.cache_key, None)
except Exception as e:
e_type = str(type(e))
if e_type != "<class 'memcache.MemcachedKeyLengthError'>":
raise e
data = None
if data is None: if data is None:
data = super(SessionStore, self).load() data = super(SessionStore, self).load()
cache.set(self.cache_key, data, settings.SESSION_COOKIE_AGE) cache.set(self.cache_key, data, settings.SESSION_COOKIE_AGE)

View File

@ -2,7 +2,9 @@ from __future__ import with_statement
from datetime import datetime, timedelta from datetime import datetime, timedelta
import shutil import shutil
import string
import tempfile import tempfile
import warnings
from django.conf import settings from django.conf import settings
from django.contrib.sessions.backends.db import SessionStore as DatabaseSession from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
@ -12,10 +14,11 @@ 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.backends.signed_cookies import SessionStore as CookieSession
from django.contrib.sessions.models import Session from django.contrib.sessions.models import Session
from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.middleware import SessionMiddleware
from django.core.cache.backends.base import CacheKeyWarning
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
from django.http import HttpResponse from django.http import HttpResponse
from django.test import TestCase, RequestFactory from django.test import TestCase, RequestFactory
from django.test.utils import override_settings from django.test.utils import override_settings, get_warnings_state, restore_warnings_state
from django.utils import timezone from django.utils import timezone
from django.utils import unittest from django.utils import unittest
@ -25,7 +28,7 @@ class SessionTestsMixin(object):
# class, which wouldn't work, and to allow different TestCase subclasses to # class, which wouldn't work, and to allow different TestCase subclasses to
# be used. # be used.
backend = None # subclasses must specify backend = None # subclasses must specify
def setUp(self): def setUp(self):
self.session = self.backend() self.session = self.backend()
@ -119,13 +122,13 @@ class SessionTestsMixin(object):
self.assertTrue(hasattr(i, '__iter__')) self.assertTrue(hasattr(i, '__iter__'))
self.assertTrue(self.session.accessed) self.assertTrue(self.session.accessed)
self.assertFalse(self.session.modified) self.assertFalse(self.session.modified)
self.assertEqual(list(i), [('x',1)]) self.assertEqual(list(i), [('x', 1)])
def test_clear(self): def test_clear(self):
self.session['x'] = 1 self.session['x'] = 1
self.session.modified = False self.session.modified = False
self.session.accessed = False self.session.accessed = False
self.assertEqual(self.session.items(), [('x',1)]) self.assertEqual(self.session.items(), [('x', 1)])
self.session.clear() self.session.clear()
self.assertEqual(self.session.items(), []) self.assertEqual(self.session.items(), [])
self.assertTrue(self.session.accessed) self.assertTrue(self.session.accessed)
@ -280,7 +283,7 @@ class DatabaseSessionTests(SessionTestsMixin, TestCase):
s = Session.objects.get(session_key=self.session.session_key) s = Session.objects.get(session_key=self.session.session_key)
# Change it # Change it
Session.objects.save(s.session_key, {'y':2}, s.expire_date) Session.objects.save(s.session_key, {'y': 2}, s.expire_date)
# Clear cache, so that it will be retrieved from DB # Clear cache, so that it will be retrieved from DB
del self.session._session_cache del self.session._session_cache
self.assertEqual(self.session['y'], 2) self.assertEqual(self.session['y'], 2)
@ -298,6 +301,14 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase):
with self.assertNumQueries(0): with self.assertNumQueries(0):
self.assertTrue(self.session.exists(self.session.session_key)) self.assertTrue(self.session.exists(self.session.session_key))
def test_load_overlong_key(self):
warnings_state = get_warnings_state()
warnings.filterwarnings('ignore',
category=CacheKeyWarning)
self.session._session_key = (string.ascii_letters + string.digits) * 20
self.assertEqual(self.session.load(), {})
restore_warnings_state(warnings_state)
CacheDBSessionWithTimeZoneTests = override_settings(USE_TZ=True)(CacheDBSessionTests) CacheDBSessionWithTimeZoneTests = override_settings(USE_TZ=True)(CacheDBSessionTests)
@ -339,6 +350,14 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
backend = CacheSession backend = CacheSession
def test_load_overlong_key(self):
warnings_state = get_warnings_state()
warnings.filterwarnings('ignore',
category=CacheKeyWarning)
self.session._session_key = (string.ascii_letters + string.digits) * 20
self.assertEqual(self.session.load(), {})
restore_warnings_state(warnings_state)
class SessionMiddlewareTests(unittest.TestCase): class SessionMiddlewareTests(unittest.TestCase):
@ -394,6 +413,7 @@ class SessionMiddlewareTests(unittest.TestCase):
self.assertNotIn('httponly', self.assertNotIn('httponly',
str(response.cookies[settings.SESSION_COOKIE_NAME])) str(response.cookies[settings.SESSION_COOKIE_NAME]))
class CookieSessionTests(SessionTestsMixin, TestCase): class CookieSessionTests(SessionTestsMixin, TestCase):
backend = CookieSession backend = CookieSession