Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed #2066: session data can now be stored in the cache or on the fi…
…lesystem. This should be fully backwards-compatible (the database cache store is still the default). A big thanks to John D'Agostino for the bulk of this code. git-svn-id: http://code.djangoproject.com/svn/django/trunk@6333 bcc190cf-cafb-0310-a4f2-bffc1f526a37
- Loading branch information
Showing
13 changed files
with
467 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,143 @@ | |||
import base64 | |||
import md5 | |||
import os | |||
import random | |||
import sys | |||
import time | |||
from django.conf import settings | |||
from django.core.exceptions import SuspiciousOperation | |||
|
|||
try: | |||
import cPickle as pickle | |||
except ImportError: | |||
import pickle | |||
|
|||
class SessionBase(object): | |||
""" | |||
Base class for all Session classes. | |||
""" | |||
|
|||
TEST_COOKIE_NAME = 'testcookie' | |||
TEST_COOKIE_VALUE = 'worked' | |||
|
|||
def __init__(self, session_key=None): | |||
self._session_key = session_key | |||
self.accessed = False | |||
self.modified = False | |||
|
|||
def __contains__(self, key): | |||
return key in self._session | |||
|
|||
def __getitem__(self, key): | |||
return self._session[key] | |||
|
|||
def __setitem__(self, key, value): | |||
self._session[key] = value | |||
self.modified = True | |||
|
|||
def __delitem__(self, key): | |||
del self._session[key] | |||
self.modified = True | |||
|
|||
def keys(self): | |||
return self._session.keys() | |||
|
|||
def items(self): | |||
return self._session.items() | |||
|
|||
def get(self, key, default=None): | |||
return self._session.get(key, default) | |||
|
|||
def pop(self, key, *args): | |||
return self._session.pop(key, *args) | |||
|
|||
def set_test_cookie(self): | |||
self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE | |||
|
|||
def test_cookie_worked(self): | |||
return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE | |||
|
|||
def delete_test_cookie(self): | |||
del self[self.TEST_COOKIE_NAME] | |||
|
|||
def encode(self, session_dict): | |||
"Returns the given session dictionary pickled and encoded as a string." | |||
pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL) | |||
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest() | |||
return base64.encodestring(pickled + pickled_md5) | |||
|
|||
def decode(self, session_data): | |||
encoded_data = base64.decodestring(session_data) | |||
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:] | |||
if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: | |||
raise SuspiciousOperation("User tampered with session cookie.") | |||
try: | |||
return pickle.loads(pickled) | |||
# Unpickling can cause a variety of exceptions. If something happens, | |||
# just return an empty dictionary (an empty session). | |||
except: | |||
return {} | |||
|
|||
def _get_new_session_key(self): | |||
"Returns session key that isn't being used." | |||
# The random module is seeded when this Apache child is created. | |||
# Use settings.SECRET_KEY as added salt. | |||
while 1: | |||
session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), | |||
os.getpid(), time.time(), settings.SECRET_KEY)).hexdigest() | |||
if not self.exists(session_key): | |||
break | |||
return session_key | |||
|
|||
def _get_session_key(self): | |||
if self._session_key: | |||
return self._session_key | |||
else: | |||
self._session_key = self._get_new_session_key() | |||
return self._session_key | |||
|
|||
def _set_session_key(self, session_key): | |||
self._session_key = session_key | |||
|
|||
session_key = property(_get_session_key, _set_session_key) | |||
|
|||
def _get_session(self): | |||
# Lazily loads session from storage. | |||
self.accessed = True | |||
try: | |||
return self._session_cache | |||
except AttributeError: | |||
if self.session_key is None: | |||
self._session_cache = {} | |||
else: | |||
self._session_cache = self.load() | |||
return self._session_cache | |||
|
|||
_session = property(_get_session) | |||
|
|||
# Methods that child classes must implement. | |||
|
|||
def exists(self, session_key): | |||
""" | |||
Returns True if the given session_key already exists. | |||
""" | |||
raise NotImplementedError | |||
|
|||
def save(self): | |||
""" | |||
Saves the session data. | |||
""" | |||
raise NotImplementedError | |||
|
|||
def delete(self, session_key): | |||
""" | |||
Clears out the session data under this key. | |||
""" | |||
raise NotImplementedError | |||
|
|||
def load(self): | |||
""" | |||
Loads the session data and returns a dictionary. | |||
""" | |||
raise NotImplementedError | |||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,26 @@ | |||
from django.conf import settings | |||
from django.contrib.sessions.backends.base import SessionBase | |||
from django.core.cache import cache | |||
|
|||
class SessionStore(SessionBase): | |||
""" | |||
A cache-based session store. | |||
""" | |||
def __init__(self, session_key=None): | |||
self._cache = cache | |||
super(SessionStore, self).__init__(session_key) | |||
|
|||
def load(self): | |||
session_data = self._cache.get(self.session_key) | |||
return session_data or {} | |||
|
|||
def save(self): | |||
self._cache.set(self.session_key, self._session, settings.SESSION_COOKIE_AGE) | |||
|
|||
def exists(self, session_key): | |||
if self._cache.get(session_key): | |||
return True | |||
return False | |||
|
|||
def delete(self, session_key): | |||
self._cache.delete(session_key) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,49 @@ | |||
from django.conf import settings | |||
from django.contrib.sessions.models import Session | |||
from django.contrib.sessions.backends.base import SessionBase | |||
from django.core.exceptions import SuspiciousOperation | |||
import datetime | |||
|
|||
class SessionStore(SessionBase): | |||
""" | |||
Implements database session store | |||
""" | |||
def __init__(self, session_key=None): | |||
super(SessionStore, self).__init__(session_key) | |||
|
|||
def load(self): | |||
try: | |||
s = Session.objects.get( | |||
session_key = self.session_key, | |||
expire_date__gt=datetime.datetime.now() | |||
) | |||
return self.decode(s.session_data) | |||
except (Session.DoesNotExist, SuspiciousOperation): | |||
|
|||
# Create a new session_key for extra security. | |||
self.session_key = self._get_new_session_key() | |||
self._session_cache = {} | |||
|
|||
# Save immediately to minimize collision | |||
self.save() | |||
return {} | |||
|
|||
def exists(self, session_key): | |||
try: | |||
Session.objects.get(session_key=session_key) | |||
except Session.DoesNotExist: | |||
return False | |||
return True | |||
|
|||
def save(self): | |||
Session.objects.create( | |||
session_key = self.session_key, | |||
session_data = self.encode(self._session), | |||
expire_date = datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE) | |||
) | |||
|
|||
def delete(self, session_key): | |||
try: | |||
Session.objects.get(session_key=session_key).delete() | |||
except Session.DoesNotExist: | |||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,67 @@ | |||
import os | |||
from django.conf import settings | |||
from django.contrib.sessions.backends.base import SessionBase | |||
from django.core.exceptions import SuspiciousOperation | |||
|
|||
class SessionStore(SessionBase): | |||
""" | |||
Implements a file based session store. | |||
""" | |||
def __init__(self, session_key=None): | |||
self.storage_path = settings.SESSION_FILE_PATH | |||
self.file_prefix = settings.SESSION_COOKIE_NAME | |||
super(SessionStore, self).__init__(session_key) | |||
|
|||
def _key_to_file(self, session_key=None): | |||
""" | |||
Get the file associated with this session key. | |||
""" | |||
if session_key is None: | |||
session_key = self.session_key | |||
|
|||
# Make sure we're not vulnerable to directory traversal. Session keys | |||
# should always be md5s, so they should never contain directory components. | |||
if os.path.sep in session_key: | |||
raise SuspiciousOperation("Invalid characters (directory components) in session key") | |||
|
|||
return os.path.join(self.storage_path, self.file_prefix + session_key) | |||
|
|||
def load(self): | |||
session_data = {} | |||
try: | |||
session_file = open(self._key_to_file(), "rb") | |||
try: | |||
session_data = self.decode(session_file.read()) | |||
except(EOFError, SuspiciousOperation): | |||
self._session_key = self._get_new_session_key() | |||
self._session_cache = {} | |||
self.save() | |||
finally: | |||
session_file.close() | |||
except(IOError): | |||
pass | |||
return session_data | |||
|
|||
def save(self): | |||
try: | |||
f = open(self._key_to_file(self.session_key), "wb") | |||
try: | |||
f.write(self.encode(self._session)) | |||
finally: | |||
f.close() | |||
except(IOError, EOFError): | |||
pass | |||
|
|||
def exists(self, session_key): | |||
if os.path.exists(self._key_to_file(session_key)): | |||
return True | |||
return False | |||
|
|||
def delete(self, session_key): | |||
try: | |||
os.unlink(self._key_to_file(session_key)) | |||
except OSError: | |||
pass | |||
|
|||
def clean(self): | |||
pass |
Oops, something went wrong.