Author: PaulM
Date: 2012-03-22 22:31:11 -0700 (Thu, 22 Mar 2012)
New Revision: 17795

Modified:
   django/trunk/django/contrib/sessions/backends/cache.py
   django/trunk/django/contrib/sessions/backends/cached_db.py
   django/trunk/django/contrib/sessions/tests.py
Log:
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.



Modified: django/trunk/django/contrib/sessions/backends/cache.py
===================================================================
--- django/trunk/django/contrib/sessions/backends/cache.py      2012-03-23 
03:29:30 UTC (rev 17794)
+++ django/trunk/django/contrib/sessions/backends/cache.py      2012-03-23 
05:31:11 UTC (rev 17795)
@@ -16,7 +16,13 @@
         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()

Modified: django/trunk/django/contrib/sessions/backends/cached_db.py
===================================================================
--- django/trunk/django/contrib/sessions/backends/cached_db.py  2012-03-23 
03:29:30 UTC (rev 17794)
+++ django/trunk/django/contrib/sessions/backends/cached_db.py  2012-03-23 
05:31:11 UTC (rev 17795)
@@ -21,7 +21,13 @@
         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)

Modified: django/trunk/django/contrib/sessions/tests.py
===================================================================
--- django/trunk/django/contrib/sessions/tests.py       2012-03-23 03:29:30 UTC 
(rev 17794)
+++ django/trunk/django/contrib/sessions/tests.py       2012-03-23 05:31:11 UTC 
(rev 17795)
@@ -2,7 +2,9 @@
 
 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.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, 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 @@
         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 @@
 
         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,7 +301,15 @@
         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,7 +350,15 @@
 
     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):
 
     @override_settings(SESSION_COOKIE_SECURE=True)
@@ -394,6 +413,7 @@
         self.assertNotIn('httponly',
                          str(response.cookies[settings.SESSION_COOKIE_NAME]))
 
+
 class CookieSessionTests(SessionTestsMixin, TestCase):
 
     backend = CookieSession

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to