Ticket #16199: 16199.5.diff
File 16199.5.diff, 10.5 KB (added by , 13 years ago) |
---|
-
new file django/contrib/sessions/backends/cookies.py
diff --git a/django/contrib/sessions/backends/cookies.py b/django/contrib/sessions/backends/cookies.py new file mode 100644 index 0000000..bff221b
- + 1 import zlib 2 try: 3 import cPickle as pickle 4 except ImportError: 5 import pickle 6 7 from django.conf import settings 8 from django.core import signing 9 from django.utils.encoding import smart_str 10 11 from django.contrib.sessions.backends.base import SessionBase 12 13 14 class SessionStore(SessionBase): 15 salt = 'django.contrib.sessions.backends.cookies' 16 17 def load(self): 18 """ 19 We load the data from the key itself instead of fetching from 20 some external data store. Opposite of _get_session_key(), 21 raises BadSignature if signature fails. 22 """ 23 signer = signing.TimestampSigner(salt=self.salt) 24 try: 25 base64d = signer.unsign( 26 self._session_key, max_age=settings.SESSION_COOKIE_AGE) 27 pickled = signing.b64_decode(smart_str(base64d)) 28 return pickle.loads(zlib.decompress(pickled)) 29 except (signing.BadSignature, ValueError): 30 self.create() 31 return {} 32 33 def create(self): 34 """ 35 To create a new key, we simply make sure that the modified flag is set 36 so that the cookie is set on the client for the current request. 37 """ 38 self.modified = True 39 40 def save(self): 41 """ 42 To save, we get the session key as a securely signed string and then 43 set the modified flag so that the cookie is set on the client for the 44 current request. 45 """ 46 self._session_key = self._get_session_key() 47 self.modified = True 48 49 def exists(self, session_key=None): 50 """ 51 This method makes sense when you're talking to a shared resource, but 52 it doesn't matter when you're storing the information in the client's 53 cookie. 54 """ 55 return False 56 57 def delete(self, session_key=None): 58 """ 59 To delete, we clear the session key and the underlying data structure 60 and set the modified flag so that the cookie is set on the client for 61 the current request. 62 """ 63 self._session_key = '' 64 self._session_cache = {} 65 self.modified = True 66 67 def cycle_key(self): 68 """ 69 Keeps the same data but with a new key. To do this, we just have to 70 call ``save()`` and it will automatically save a cookie with a new key 71 at the end of the request. 72 """ 73 self.save() 74 75 def _get_session_key(self): 76 """ 77 Most session backends don't need to override this method, but we do, 78 because instead of generating a random string, we want to actually 79 generate a secure url-safe Base64-encoded string of data as our 80 session key. 81 """ 82 payload = getattr(self, '_session_cache', {}) 83 pickled = pickle.dumps(payload, pickle.HIGHEST_PROTOCOL) 84 base64d = signing.b64_encode(zlib.compress(pickled)) 85 return signing.TimestampSigner(salt=self.salt).sign(base64d) 86 87 def _set_session_key(self, session_key): 88 self._session_key = session_key 89 90 session_key = property(_get_session_key, _set_session_key) -
django/contrib/sessions/tests.py
diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 2eb43f3..0c34101 100644
a b from django.contrib.sessions.backends.db import SessionStore as DatabaseSession 7 7 from django.contrib.sessions.backends.cache import SessionStore as CacheSession 8 8 from django.contrib.sessions.backends.cached_db import SessionStore as CacheDBSession 9 9 from django.contrib.sessions.backends.file import SessionStore as FileSession 10 from django.contrib.sessions.backends.cookies import SessionStore as CookieSession 10 11 from django.contrib.sessions.models import Session 11 12 from django.contrib.sessions.middleware import SessionMiddleware 12 13 from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation 13 14 from django.http import HttpResponse 14 15 from django.test import TestCase, RequestFactory 16 from django.test.utils import override_settings 15 17 from django.utils import unittest 16 18 17 19 … … class SessionTestsMixin(object): 214 216 # Tests get_expire_at_browser_close with different settings and different 215 217 # set_expiry calls 216 218 try: 217 try: 218 original_expire_at_browser_close = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE 219 settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = False 220 219 with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=False): 221 220 self.session.set_expiry(10) 222 221 self.assertFalse(self.session.get_expire_at_browser_close()) 223 222 … … class SessionTestsMixin(object): 227 226 self.session.set_expiry(None) 228 227 self.assertFalse(self.session.get_expire_at_browser_close()) 229 228 230 settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = True 231 229 with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=True): 232 230 self.session.set_expiry(10) 233 231 self.assertFalse(self.session.get_expire_at_browser_close()) 234 232 … … class SessionTestsMixin(object): 237 235 238 236 self.session.set_expiry(None) 239 237 self.assertTrue(self.session.get_expire_at_browser_close()) 240 241 except: 242 raise 243 finally: 244 settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = original_expire_at_browser_close 238 except: 239 raise 245 240 246 241 def test_decode(self): 247 242 # Ensure we can decode what we encode … … class FileSessionTests(SessionTestsMixin, unittest.TestCase): 302 297 shutil.rmtree(self.temp_session_store) 303 298 super(FileSessionTests, self).tearDown() 304 299 300 @override_settings( 301 SESSION_FILE_PATH="/if/this/directory/exists/you/have/a/weird/computer") 305 302 def test_configuration_check(self): 306 303 # Make sure the file backend checks for a good storage dir 307 settings.SESSION_FILE_PATH = "/if/this/directory/exists/you/have/a/weird/computer"308 304 self.assertRaises(ImproperlyConfigured, self.backend) 309 305 310 306 def test_invalid_key_backslash(self): … … class CacheSessionTests(SessionTestsMixin, unittest.TestCase): 324 320 325 321 326 322 class SessionMiddlewareTests(unittest.TestCase): 327 def setUp(self):328 self.old_SESSION_COOKIE_SECURE = settings.SESSION_COOKIE_SECURE329 self.old_SESSION_COOKIE_HTTPONLY = settings.SESSION_COOKIE_HTTPONLY330 331 def tearDown(self):332 settings.SESSION_COOKIE_SECURE = self.old_SESSION_COOKIE_SECURE333 settings.SESSION_COOKIE_HTTPONLY = self.old_SESSION_COOKIE_HTTPONLY334 323 324 @override_settings(SESSION_COOKIE_SECURE=True) 335 325 def test_secure_session_cookie(self): 336 settings.SESSION_COOKIE_SECURE = True337 338 326 request = RequestFactory().get('/') 339 327 response = HttpResponse('Session test') 340 328 middleware = SessionMiddleware() … … class SessionMiddlewareTests(unittest.TestCase): 347 335 response = middleware.process_response(request, response) 348 336 self.assertTrue(response.cookies[settings.SESSION_COOKIE_NAME]['secure']) 349 337 338 @override_settings(SESSION_COOKIE_HTTPONLY=True) 350 339 def test_httponly_session_cookie(self): 351 settings.SESSION_COOKIE_HTTPONLY = True352 353 340 request = RequestFactory().get('/') 354 341 response = HttpResponse('Session test') 355 342 middleware = SessionMiddleware() … … class SessionMiddlewareTests(unittest.TestCase): 361 348 # Handle the response through the middleware 362 349 response = middleware.process_response(request, response) 363 350 self.assertTrue(response.cookies[settings.SESSION_COOKIE_NAME]['httponly']) 351 352 353 class CookieSessionTests(SessionTestsMixin, TestCase): 354 355 backend = CookieSession 356 357 def test_save(self): 358 """ 359 This test tested exists() in the other session backends, but that 360 doesn't make sense for us. 361 """ 362 pass -
django/core/signing.py
diff --git a/django/core/signing.py b/django/core/signing.py index 3165cf8..224d942 100644
a b class TimestampSigner(Signer): 161 161 def __init__(self, *args, **kwargs): 162 162 self.time_func = kwargs.pop('time', time.time) 163 163 super(TimestampSigner, self).__init__(*args, **kwargs) 164 164 165 165 def timestamp(self): 166 166 return baseconv.base62.encode(int(self.time_func() * 10000)) 167 167 -
docs/topics/http/sessions.txt
diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 8529f53..9d19acc 100644
a b defaults to output from ``tempfile.gettempdir()``, most likely ``/tmp``) to 95 95 control where Django stores session files. Be sure to check that your Web 96 96 server has permissions to read and write to this location. 97 97 98 Using cookie-based sessions 99 --------------------------- 100 101 .. versionadded:: 1.4 102 103 To use cookies-based sessions, set the :setting:`SESSION_ENGINE` setting to 104 ``"django.contrib.sessions.backends.cookies"``. The session data will be 105 stored using Django's tools for :doc:`cryptographic signing </topics/signing>` 106 and the :setting:`SECRET_KEY` setting. It's recommended to set the 107 :setting:`SESSION_COOKIE_HTTPONLY` to ``True`` to prevent tampering from 108 JavaScript. 109 110 .. warning:: 111 112 The session data is **signed but not encrypted**! 113 114 When using the cookies backend the session data can be read out 115 and will be invalidated when being tampered with. The same invalidation 116 happens if the client storing the cookie (e.g. your user's browser) 117 can't store all of the session cookie and drops data. Even though 118 Django compresses the data before it's still entirely possible to 119 exceed the `common limit of 4096 bytes`_ per cookie. 120 121 Also, the size of a cookie can have an impact on the `speed of your site`_. 122 123 .. _`common limit of 4096 bytes`: http://tools.ietf.org/html/rfc2965#section-5.3 124 .. _`speed of your site`: http://yuiblog.com/blog/2007/03/01/performance-research-part-3/ 98 125 99 126 Using sessions in views 100 127 ======================= … … Controls where Django stores session data. Valid values are: 420 447 * ``'django.contrib.sessions.backends.file'`` 421 448 * ``'django.contrib.sessions.backends.cache'`` 422 449 * ``'django.contrib.sessions.backends.cached_db'`` 450 * ``'django.contrib.sessions.backends.cookies'`` 423 451 424 452 See `configuring the session engine`_ for more details. 425 453