Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit 2ca980195653a754a1c4726f5bb253147c6cb329 1 parent 3806122
Paul McMillan authored March 23, 2012
8  django/contrib/sessions/backends/cache.py
@@ -16,7 +16,13 @@ def cache_key(self):
16 16
         return KEY_PREFIX + self._get_or_create_session_key()
17 17
 
18 18
     def load(self):
19  
-        session_data = self._cache.get(self.cache_key)
  19
+        try:
  20
+            session_data = self._cache.get(self.cache_key, None)
  21
+        except Exception as e:
  22
+            e_type = str(type(e))
  23
+            if e_type != "<class 'memcache.MemcachedKeyLengthError'>":
  24
+                raise e
  25
+            session_data = None
20 26
         if session_data is not None:
21 27
             return session_data
22 28
         self.create()
8  django/contrib/sessions/backends/cached_db.py
@@ -21,7 +21,13 @@ def cache_key(self):
21 21
         return KEY_PREFIX + self._get_or_create_session_key()
22 22
 
23 23
     def load(self):
24  
-        data = cache.get(self.cache_key, None)
  24
+        try:
  25
+            data = cache.get(self.cache_key, None)
  26
+        except Exception as e:
  27
+            e_type = str(type(e))
  28
+            if e_type != "<class 'memcache.MemcachedKeyLengthError'>":
  29
+                raise e
  30
+            data = None
25 31
         if data is None:
26 32
             data = super(SessionStore, self).load()
27 33
             cache.set(self.cache_key, data, settings.SESSION_COOKIE_AGE)
30  django/contrib/sessions/tests.py
@@ -2,7 +2,9 @@
2 2
 
3 3
 from datetime import datetime, timedelta
4 4
 import shutil
  5
+import string
5 6
 import tempfile
  7
+import warnings
6 8
 
7 9
 from django.conf import settings
8 10
 from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
@@ -12,10 +14,11 @@
12 14
 from django.contrib.sessions.backends.signed_cookies import SessionStore as CookieSession
13 15
 from django.contrib.sessions.models import Session
14 16
 from django.contrib.sessions.middleware import SessionMiddleware
  17
+from django.core.cache.backends.base import CacheKeyWarning
15 18
 from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
16 19
 from django.http import HttpResponse
17 20
 from django.test import TestCase, RequestFactory
18  
-from django.test.utils import override_settings
  21
+from django.test.utils import override_settings, get_warnings_state, restore_warnings_state
19 22
 from django.utils import timezone
20 23
 from django.utils import unittest
21 24
 
@@ -25,7 +28,7 @@ class SessionTestsMixin(object):
25 28
     # class, which wouldn't work, and to allow different TestCase subclasses to
26 29
     # be used.
27 30
 
28  
-    backend = None # subclasses must specify
  31
+    backend = None  # subclasses must specify
29 32
 
30 33
     def setUp(self):
31 34
         self.session = self.backend()
@@ -119,13 +122,13 @@ def test_iteritems(self):
119 122
         self.assertTrue(hasattr(i, '__iter__'))
120 123
         self.assertTrue(self.session.accessed)
121 124
         self.assertFalse(self.session.modified)
122  
-        self.assertEqual(list(i), [('x',1)])
  125
+        self.assertEqual(list(i), [('x', 1)])
123 126
 
124 127
     def test_clear(self):
125 128
         self.session['x'] = 1
126 129
         self.session.modified = False
127 130
         self.session.accessed = False
128  
-        self.assertEqual(self.session.items(), [('x',1)])
  131
+        self.assertEqual(self.session.items(), [('x', 1)])
129 132
         self.session.clear()
130 133
         self.assertEqual(self.session.items(), [])
131 134
         self.assertTrue(self.session.accessed)
@@ -280,7 +283,7 @@ def test_sessionmanager_save(self):
280 283
 
281 284
         s = Session.objects.get(session_key=self.session.session_key)
282 285
         # Change it
283  
-        Session.objects.save(s.session_key, {'y':2}, s.expire_date)
  286
+        Session.objects.save(s.session_key, {'y': 2}, s.expire_date)
284 287
         # Clear cache, so that it will be retrieved from DB
285 288
         del self.session._session_cache
286 289
         self.assertEqual(self.session['y'], 2)
@@ -298,6 +301,14 @@ def test_exists_searches_cache_first(self):
298 301
         with self.assertNumQueries(0):
299 302
             self.assertTrue(self.session.exists(self.session.session_key))
300 303
 
  304
+    def test_load_overlong_key(self):
  305
+        warnings_state = get_warnings_state()
  306
+        warnings.filterwarnings('ignore',
  307
+                                category=CacheKeyWarning)
  308
+        self.session._session_key = (string.ascii_letters + string.digits) * 20
  309
+        self.assertEqual(self.session.load(), {})
  310
+        restore_warnings_state(warnings_state)
  311
+
301 312
 
302 313
 CacheDBSessionWithTimeZoneTests = override_settings(USE_TZ=True)(CacheDBSessionTests)
303 314
 
@@ -339,6 +350,14 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
339 350
 
340 351
     backend = CacheSession
341 352
 
  353
+    def test_load_overlong_key(self):
  354
+        warnings_state = get_warnings_state()
  355
+        warnings.filterwarnings('ignore',
  356
+                                category=CacheKeyWarning)
  357
+        self.session._session_key = (string.ascii_letters + string.digits) * 20
  358
+        self.assertEqual(self.session.load(), {})
  359
+        restore_warnings_state(warnings_state)
  360
+
342 361
 
343 362
 class SessionMiddlewareTests(unittest.TestCase):
344 363
 
@@ -394,6 +413,7 @@ def test_no_httponly_session_cookie(self):
394 413
         self.assertNotIn('httponly',
395 414
                          str(response.cookies[settings.SESSION_COOKIE_NAME]))
396 415
 
  416
+
397 417
 class CookieSessionTests(SessionTestsMixin, TestCase):
398 418
 
399 419
     backend = CookieSession

0 notes on commit 2ca9801

Please sign in to comment.
Something went wrong with that request. Please try again.