Ticket #2066: session_engines.diff

File session_engines.diff, 25.6 KB (added by John D'Agostino <john.dagostino@…>, 17 years ago)

configurable session_engines

  • django/test/client.py

     
    44from urlparse import urlparse
    55from django.conf import settings
    66from django.contrib.auth import authenticate, login
    7 from django.contrib.sessions.models import Session
    8 from django.contrib.sessions.middleware import SessionWrapper
    97from django.core.handlers.base import BaseHandler
    108from django.core.handlers.wsgi import WSGIRequest
    119from django.core.signals import got_request_exception
     
    129127    def _session(self):
    130128        "Obtain the current session variables"
    131129        if 'django.contrib.sessions' in settings.INSTALLED_APPS:
     130            engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
    132131            cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
    133132            if cookie:
    134                 return SessionWrapper(cookie.value)
     133                return engine.SessionClass(cookie.value)
    135134        return {}
    136135    session = property(_session)
    137136   
     
    229228        """
    230229        user = authenticate(**credentials)
    231230        if user and 'django.contrib.sessions' in settings.INSTALLED_APPS:
    232             obj = Session.objects.get_new_session_object()
    233 
     231            engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
     232           
    234233            # Create a fake request to store login details
    235234            request = HttpRequest()
    236             request.session = SessionWrapper(obj.session_key)
     235            request.session = engine.SessionClass()
    237236            login(request, user)
    238 
     237           
    239238            # Set the cookie to represent the session
    240             self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key
     239            self.cookies[settings.SESSION_COOKIE_NAME] = request.session.session_key
    241240            self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
    242241            self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
    243242            self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
    244243            self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
    245244            self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
    246245
    247             # Set the session values
    248             Session.objects.save(obj.session_key, request.session._session,
    249                 datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))       
     246            # Save the session values
     247            request.session.save()       
    250248
    251249            return True
    252250        else:
  • django/conf/global_settings.py

     
    270270SESSION_COOKIE_SECURE = False             # Whether the session cookie should be secure (https:// only).
    271271SESSION_SAVE_EVERY_REQUEST = False        # Whether to save the session data on every request.
    272272SESSION_EXPIRE_AT_BROWSER_CLOSE = False   # Whether sessions expire when a user closes his browser.
     273SESSION_ENGINE = 'django.contrib.sessions.engines.db' # The module to store session data
     274SESSION_FILE_PATH = '/tmp/'               # Directory to store session files if using the file session module
    273275
    274276#########
    275277# CACHE #
  • django/contrib/sessions/middleware.py

     
    11from django.conf import settings
    2 from django.contrib.sessions.models import Session
    3 from django.core.exceptions import SuspiciousOperation
    42from django.utils.cache import patch_vary_headers
    53import datetime
    64
    7 TEST_COOKIE_NAME = 'testcookie'
    8 TEST_COOKIE_VALUE = 'worked'
    9 
    10 class SessionWrapper(object):
    11     def __init__(self, session_key):
    12         self.session_key = session_key
    13         self.accessed = False
    14         self.modified = False
    15 
    16     def __contains__(self, key):
    17         return key in self._session
    18 
    19     def __getitem__(self, key):
    20         return self._session[key]
    21 
    22     def __setitem__(self, key, value):
    23         self._session[key] = value
    24         self.modified = True
    25 
    26     def __delitem__(self, key):
    27         del self._session[key]
    28         self.modified = True
    29 
    30     def keys(self):
    31         return self._session.keys()
    32 
    33     def items(self):
    34         return self._session.items()
    35 
    36     def get(self, key, default=None):
    37         return self._session.get(key, default)
    38 
    39     def pop(self, key, *args):
    40         return self._session.pop(key, *args)
    41 
    42     def set_test_cookie(self):
    43         self[TEST_COOKIE_NAME] = TEST_COOKIE_VALUE
    44 
    45     def test_cookie_worked(self):
    46         return self.get(TEST_COOKIE_NAME) == TEST_COOKIE_VALUE
    47 
    48     def delete_test_cookie(self):
    49         del self[TEST_COOKIE_NAME]
    50 
    51     def _get_session(self):
    52         # Lazily loads session from storage.
    53         self.accessed = True
    54         try:
    55             return self._session_cache
    56         except AttributeError:
    57             if self.session_key is None:
    58                 self._session_cache = {}
    59             else:
    60                 try:
    61                     s = Session.objects.get(session_key=self.session_key,
    62                         expire_date__gt=datetime.datetime.now())
    63                     self._session_cache = s.get_decoded()
    64                 except (Session.DoesNotExist, SuspiciousOperation):
    65                     self._session_cache = {}
    66                     # Set the session_key to None to force creation of a new
    67                     # key, for extra security.
    68                     self.session_key = None
    69             return self._session_cache
    70 
    71     _session = property(_get_session)
    72 
    735class SessionMiddleware(object):
     6    """
     7    Django Session Middleware
     8    """
    749    def process_request(self, request):
    75         request.session = SessionWrapper(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
     10        engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
     11        request.session = engine.SessionStore(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
    7612
    7713    def process_response(self, request, response):
    7814        # If request.session was modified, or if response.session was set, save
     
    8622            if accessed:
    8723                patch_vary_headers(response, ('Cookie',))
    8824            if modified or settings.SESSION_SAVE_EVERY_REQUEST:
    89                 if request.session.session_key:
    90                     session_key = request.session.session_key
    91                 else:
    92                     obj = Session.objects.get_new_session_object()
    93                     session_key = obj.session_key
    94 
    9525                if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
    9626                    max_age = None
    9727                    expires = None
    9828                else:
    9929                    max_age = settings.SESSION_COOKIE_AGE
    100                     expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
    101                 new_session = Session.objects.save(session_key, request.session._session,
    102                     datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
    103                 response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
     30                    expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \
     31                    datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
     32                    request.session.save()
     33                response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key,
    10434                    max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
    10535                    secure=settings.SESSION_COOKIE_SECURE or None)
    10636        return response
  • django/contrib/sessions/engines/base.py

     
     1from django.conf import settings
     2from django.utils.cache import patch_vary_headers
     3import base64, md5, datetime, random, os, sys
     4import cPickle as pickle
     5
     6TEST_COOKIE_NAME = 'testcookie'
     7TEST_COOKIE_VALUE = 'worked'
     8
     9class SessionBase(object):
     10    """
     11    MetaClass for all Session classes.
     12    """
     13    def __init__(self, session_key=None):
     14        self._session_key = session_key
     15        self.accessed = False
     16        self.modified = False
     17
     18    def __contains__(self, key):
     19        return key in self._session
     20
     21    def __getitem__(self, key):
     22        return self._session[key]
     23
     24    def __setitem__(self, key, value):
     25        self._session[key] = value
     26        self.modified = True
     27
     28    def __delitem__(self, key):
     29        del self._session[key]
     30        self.modified = True
     31
     32    def keys(self):
     33        return self._session.keys()
     34
     35    def items(self):
     36        return self._session.items()
     37
     38    def get(self, key, default=None):
     39        return self._session.get(key, default)
     40
     41    def pop(self, key, *args):
     42        return self._session.pop(key, *args)
     43
     44    def set_test_cookie(self):
     45        self[TEST_COOKIE_NAME] = TEST_COOKIE_VALUE
     46
     47    def test_cookie_worked(self):
     48        return self.get(TEST_COOKIE_NAME) == TEST_COOKIE_VALUE
     49
     50    def delete_test_cookie(self):
     51        del self[TEST_COOKIE_NAME]
     52       
     53    def encode(self, session_dict):
     54        "Returns the given session dictionary pickled and encoded as a string."
     55        pickled = pickle.dumps(session_dict)
     56        pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
     57        return base64.encodestring(pickled + pickled_md5)
     58
     59    def decode(self, session_data):
     60        encoded_data = base64.decodestring(session_data)
     61        pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
     62        if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
     63            from django.core.exceptions import SuspiciousOperation
     64            raise SuspiciousOperation, "User tampered with session cookie."
     65        try:
     66            return pickle.loads(pickled)
     67        # Unpickling can cause a variety of exceptions. If something happens,
     68        # just return an empty dictionary (an empty session).
     69        except:
     70            return {}
     71       
     72    def _get_new_session_key(self):
     73        "Returns session key that isn't being used."
     74        # The random module is seeded when this Apache child is created.
     75        # Use person_id and SECRET_KEY as added salt.
     76        while 1:
     77            session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1),
     78                                  os.getpid(), time.time(), settings.SECRET_KEY)).hexdigest()
     79            if not self.exists(session_key):
     80                break
     81        return session_key
     82       
     83    def _get_session_key(self):
     84        if self._session_key:
     85            return self._session_key
     86        else:
     87            self._session_key = self._get_new_session_key()
     88            return self._session_key
     89   
     90    def _set_session_key(self, session_key):
     91        self._session_key = session_key
     92   
     93    session_key = property(_get_session_key, _set_session_key)
     94
     95    def _get_session(self):
     96        # Lazily loads session from storage.
     97        self.accessed = True
     98        try:
     99            return self._session_cache
     100        except AttributeError:
     101            if self.session_key is None:
     102                self._session_cache = {}
     103            else:
     104                self._session_cache = self.load()
     105        return self._session_cache
     106
     107    _session = property(_get_session)
     108   
     109    def exists(self, session_key):
     110        """
     111        returns True if the the session_key already exists in the
     112        session store
     113        """
     114        raise NotImplementedError
     115
     116    def save(self):
     117        """
     118        saves self._session associated self.session_key in the
     119        session store
     120        """
     121        raise NotImplementedError
     122
     123    def delete(self, session_key):
     124        """
     125        deletes the session data associated with session_key
     126        """
     127        raise NotImplementedError
     128
     129    def load(self):
     130        """
     131        returns a dictionary with the session data from the session
     132        store associated with self.session_key
     133        """
     134        raise NotImplementedError
     135       
  • django/contrib/sessions/engines/file.py

     
     1import base64, md5, random, sys, datetime, os
     2import cPickle as pickle
     3from django.conf import settings
     4from django.contrib.sessions.models import Session
     5from django.contrib.sessions.engines.base import SessionBase
     6from django.core.exceptions import SuspiciousOperation
     7from django.utils.cache import patch_vary_headers
     8import datetime
     9
     10class SessionStore(SessionBase):
     11    """
     12    Implements a file based session store.
     13    """
     14    def __init__(self, session_key=None):
     15        self.storage_path = settings.SESSION_FILE_PATH
     16        self.file_prefix = settings.SESSION_COOKIE_NAME   
     17        super(SessionStore, self).__init__(session_key)
     18   
     19    def _key_to_file(self, session_key=None):
     20        if session_key is None:
     21            session_key = self.session_key
     22        return os.path.join(self.storage_path, self.file_prefix + session_key)
     23           
     24    def load(self):
     25        session_data = {}
     26        try:
     27            session_file = open(self._key_to_file(), "rb")
     28            try:
     29                session_data = self.decode(session_file.read())
     30            except(EOFError, SuspiciousOperation):
     31                self._session_key = self._get_new_session_key()
     32                self._session_cache = {}
     33                #save to minimize collision
     34                self.save()
     35            finally:
     36                session_file.close()
     37        except(IOError):
     38            pass
     39        return session_data
     40
     41    def save(self):
     42        try:
     43            f = open(self._key_to_file(self.session_key), "wb")
     44            try:
     45                f.write(self.encode(self._session))
     46            finally:
     47                f.close()
     48        except(IOError, EOFError):
     49            pass
     50
     51    def exists(self, session_key):
     52        if os.path.exists(self._key_to_file(session_key)):
     53            return True
     54        return False
     55       
     56    def delete(self, session_key):
     57        try:
     58            os.unlink(self._key_to_file(session_key))
     59        except OSError:
     60            pass
     61           
     62    def clean(self):
     63        pass
     64 No newline at end of file
  • django/contrib/sessions/engines/cache.py

     
     1import base64, md5, random, sys, datetime, os
     2from django.conf import settings
     3from django.contrib.sessions.engines.base import SessionBase
     4from django.core.exceptions import SuspiciousOperation
     5from django.utils.cache import patch_vary_headers
     6from django.core.cache import cache
     7
     8class SessionStore(SessionBase):
     9    """
     10    Implements a cache based session store.
     11    """
     12    def __init__(self, session_key=None):
     13        self._cache = cache
     14        super(SessionStore, self).__init__(session_key)
     15       
     16    def load(self):
     17        session_data = self._cache.get(self.session_key)
     18        return session_data
     19
     20    def save(self):
     21        self._cache.set(self.session_key, self._session, settings.SESSION_COOKIE_AGE)
     22
     23    def exists(self, session_key):
     24        if self._cache.get(session_key):
     25                return True
     26        return False
     27       
     28    def delete(self, session_key):
     29        self._cache.delete(session_key)
     30 No newline at end of file
  • django/contrib/sessions/engines/db.py

     
     1from django.conf import settings
     2from django.contrib.sessions.models import Session
     3from django.contrib.sessions.engines.base import SessionBase
     4from django.core.exceptions import SuspiciousOperation
     5import datetime
     6
     7class SessionStore(SessionBase):
     8    """
     9    Implements database session store
     10    """
     11    def __init__(self, session_key=None):
     12        super(SessionStore, self).__init__(session_key)
     13   
     14    def load(self):
     15        try:
     16            s = Session.objects.get(session_key=self.session_key,
     17                    expire_date__gt=datetime.datetime.now())
     18            return self.decode(s.session_data)
     19        except (Session.DoesNotExist, SuspiciousOperation):
     20            # Create a new session_key for extra security.
     21            self.session_key = self._get_new_session_key()
     22            self._session_cache = {}
     23            #save to minimize collision
     24            self.save()
     25            return {}
     26           
     27    def exists(self, session_key):
     28        try:
     29            Session.objects.get(session_key=session_key)
     30        except Session.DoesNotExist:
     31            return False
     32        return True
     33           
     34    def save(self):
     35        s = Session(self.session_key, self.encode(self._session), datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
     36        s.save()
     37   
     38    def delete(self, session_key):
     39        try:
     40            s = Session.objects.get(session_key=session_key)
     41            s.delete()
     42        except Session.DoesNotExist:
     43            pass
     44 No newline at end of file
  • django/contrib/sessions/tests.py

     
    11r"""
    2 >>> s = SessionWrapper(None)
     2>>> db_session = DatabaseSession()
     3>>> db_session.modified
     4False
     5>>> db_session['cat'] = "dog"
     6>>> db_session.modified
     7True
     8>>> db_session.pop('cat')
     9'dog'
     10>>> db_session.pop('some key', 'does not exist')
     11'does not exist'
     12>>> db_session.save()
     13>>> db_session.exists(db_session.session_key)
     14True
     15>>> db_session.delete(db_session.session_key)
     16>>> db_session.exists(db_session.session_key)
     17False
    318
    4 Inject data into the session cache.
    5 >>> s._session_cache = {}
    6 >>> s._session_cache['some key'] = 'exists'
     19>>> file_session = FileSession()
     20>>> file_session.modified
     21False
     22>>> file_session['cat'] = "dog"
     23>>> file_session.modified
     24True
     25>>> file_session.pop('cat')
     26'dog'
     27>>> file_session.pop('some key', 'does not exist')
     28'does not exist'
     29>>> file_session.save()
     30>>> file_session.exists(file_session.session_key)
     31True
     32>>> file_session.delete(file_session.session_key)
     33>>> file_session.exists(file_session.session_key)
     34False
    735
    8 >>> s.pop('some key')
    9 'exists'
    10 
    11 >>> s.pop('some key', 'does not exist')
     36>>> cache_session = CacheSession()
     37>>> cache_session.modified
     38False
     39>>> cache_session['cat'] = "dog"
     40>>> cache_session.modified
     41True
     42>>> cache_session.pop('cat')
     43'dog'
     44>>> cache_session.pop('some key', 'does not exist')
    1245'does not exist'
     46>>> cache_session.save()
     47>>> cache_session.exists(cache_session.session_key)
     48True
     49>>> cache_session.delete(cache_session.session_key)
     50>>> cache_session.exists(cache_session.session_key)
     51False
    1352"""
    1453
    15 from django.contrib.sessions.middleware import SessionWrapper
     54from django.contrib.sessions.engines.db import SessionStore as DatabaseSession
     55from django.contrib.sessions.engines.cache import SessionStore as CacheSession
     56from django.contrib.sessions.engines.file import SessionStore as FileSession
     57from django.conf import settings
    1658
    1759if __name__ == '__main__':
    1860    import doctest
  • django/contrib/sessions/models.py

     
    1 import base64, md5, random, sys, datetime
     1import base64, md5, random, sys, datetime, os, time
    22import cPickle as pickle
    33from django.db import models
    44from django.utils.translation import gettext_lazy as _
     
    1414    def get_new_session_key(self):
    1515        "Returns session key that isn't being used."
    1616        # The random module is seeded when this Apache child is created.
    17         # Use person_id and SECRET_KEY as added salt.
     17        # Use SECRET_KEY as added salt.
    1818        while 1:
    19             session_key = md5.new(str(random.randint(0, sys.maxint - 1)) + str(random.randint(0, sys.maxint - 1)) + settings.SECRET_KEY).hexdigest()
     19            session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1),
     20                                    os.getpid(), time.time(), settings.SECRET_KEY)).hexdigest()
    2021            try:
    2122                self.get(session_key=session_key)
    2223            except self.model.DoesNotExist:
  • docs/sessions.txt

     
    1010Enabling sessions
    1111=================
    1212
    13 Sessions are implemented via a piece of middleware_ and a Django model.
     13Sessions are implemented via a piece of middleware_.
    1414
    15 To enable session functionality, do these two things:
     15To enable session functionality, do the following:
    1616
    1717    * Edit the ``MIDDLEWARE_CLASSES`` setting and make sure
    1818      ``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``.
    1919      The default ``settings.py`` created by ``django-admin.py startproject`` has
    2020      ``SessionMiddleware`` activated.
    2121
    22     * Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, and
    23       run ``manage.py syncdb`` to install the single database table that stores
    24       session data.
     22    * If using the default database session engine add ``'django.contrib.sessions'``
     23      to your ``INSTALLED_APPS`` setting, and run ``manage.py syncdb`` to install
     24      the single database table that stores session data.
     25     
     26    * If using the file based session engine set the ``SESSION_FILE_PATH`` setting in
     27      ``settings.py`` to the directory that you want sessions stored.
     28     
     29    * If using the cache based session engine ensure that the Django cache_ framework
     30      is configured correctly.
    2531
    2632If you don't want to use sessions, you might as well remove the
    2733``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'``
    2834from your ``INSTALLED_APPS``. It'll save you a small bit of overhead.
    2935
    3036.. _middleware: ../middleware/
     37.. _cache: ../cache/
    3138
    3239Using sessions in views
    3340=======================
     
    6572      cookies. Due to the way cookies work, you won't be able to test this
    6673      until the user's next page request. See "Setting test cookies" below for
    6774      more information.
    68 
     75`
    6976    * ``test_cookie_worked()``
    7077      Returns either ``True`` or ``False``, depending on whether the user's
    7178      browser accepted the test cookie. Due to the way cookies work, you'll
     
    153160Using sessions out of views
    154161===========================
    155162
    156 Internally, each session is just a normal Django model. The ``Session`` model
     163The ``SessionStore`` which implements the session storage method can be imported
     164and a API is available to manipulate the session data outside of a view::
     165
     166    >>> from django.contrib.sessions.engines.db import SessionStore
     167    >>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead')
     168    >>> s['last_login'] = datetime.datetime(2005, 8, 20, 13, 35, 10)
     169    >>> s['last_login']
     170    datetime.datetime(2005, 8, 20, 13, 35, 0)
     171    >>> s.save()
     172
     173Or if you are using the ``django.contrib.sessions.engine.db`` each
     174session is just a normal Django model. The ``Session`` model
    157175is defined in ``django/contrib/sessions/models.py``. Because it's a normal
    158176model, you can access sessions using the normal Django database API::
    159177
    160178    >>> from django.contrib.sessions.models import Session
    161179    >>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
    162180    >>> s.expire_date
    163     datetime.datetime(2005, 8, 20, 13, 35, 12)
     181    datetime.datetime(2005, 8, 20, 13, 35, 12) 
    164182
    165183Note that you'll need to call ``get_decoded()`` to get the session dictionary.
    166184This is necessary because the dictionary is stored in an encoded format::
     
    239257
    240258A few `Django settings`_ give you control over session behavior:
    241259
     260**New in Django development version**
     261
     262SESSION_ENGINE
     263--------------
     264
     265Default: ``django.contrib.sessions.engines.db``
     266
     267Django sessions have configurable backends for the method of storage. The
     268setting ``SESSION_ENGINE`` in ``settings.py`` refers to the module which implements
     269the session backend.
     270Django comes with three session engines:
     271
     272    * ``'django.contrib.sessions.engines.db'`` is the default session storage method.
     273      All data is encoded and stored in the database.
     274     
     275    * ``'django.contrib.sessions.engines.file'`` a simple file based session storage
     276      method. Each session is encoded and stored in individual files on the server.
     277     
     278    * ``'django.contrib.sessions.engines.cache'`` based on the Django cache_ framework.
     279      Sessions are stored in the configured cache backend.
     280   
     281.. _cache: ../cache/
     282
     283SESSION_FILE_PATH
     284-----------------
     285
     286Default: ``/tmp/``
     287
     288If the module associated with ``SESSION_ENGINE`` is ``'django.contrib.sessions.file'``
     289this is the directory which session files are stored in.
     290
    242291SESSION_COOKIE_AGE
    243292------------------
    244293
Back to Top