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()
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:
return session_data
self.create()

View File

@ -21,7 +21,13 @@ class SessionStore(DBStore):
return KEY_PREFIX + self._get_or_create_session_key()
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:
data = super(SessionStore, self).load()
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
import shutil
import string
import tempfile
import warnings
from django.conf import settings
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.models import Session
from django.contrib.sessions.middleware import SessionMiddleware
from django.core.cache.backends.base import CacheKeyWarning
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
from django.http import HttpResponse
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 unittest
@ -25,7 +28,7 @@ class SessionTestsMixin(object):
# class, which wouldn't work, and to allow different TestCase subclasses to
# be used.
backend = None # subclasses must specify
backend = None # subclasses must specify
def setUp(self):
self.session = self.backend()
@ -119,13 +122,13 @@ class SessionTestsMixin(object):
self.assertTrue(hasattr(i, '__iter__'))
self.assertTrue(self.session.accessed)
self.assertFalse(self.session.modified)
self.assertEqual(list(i), [('x',1)])
self.assertEqual(list(i), [('x', 1)])
def test_clear(self):
self.session['x'] = 1
self.session.modified = False
self.session.accessed = False
self.assertEqual(self.session.items(), [('x',1)])
self.assertEqual(self.session.items(), [('x', 1)])
self.session.clear()
self.assertEqual(self.session.items(), [])
self.assertTrue(self.session.accessed)
@ -280,7 +283,7 @@ class DatabaseSessionTests(SessionTestsMixin, TestCase):
s = Session.objects.get(session_key=self.session.session_key)
# 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
del self.session._session_cache
self.assertEqual(self.session['y'], 2)
@ -298,6 +301,14 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase):
with self.assertNumQueries(0):
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)
@ -339,6 +350,14 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
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):
@ -394,6 +413,7 @@ class SessionMiddlewareTests(unittest.TestCase):
self.assertNotIn('httponly',
str(response.cookies[settings.SESSION_COOKIE_NAME]))
class CookieSessionTests(SessionTestsMixin, TestCase):
backend = CookieSession