Django

Code

Changeset 8340

Show
Ignore:
Timestamp:
08/13/08 22:57:18 (4 months ago)
Author:
mtredinnick
Message:

Added guaranteed atomic creation of new session objects. Slightly backwards
incompatible for custom session backends.

Whilst we were in the neighbourhood, use a larger range of session key values
to save a small amount of time and use the hardware-base random numbers where
available (transparently falls back to pseudo-RNG otherwise).

Fixed #1080

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/contrib/sessions/backends/base.py

    r8193 r8340  
    1414from django.utils.hashcompat import md5_constructor 
    1515 
     16# Use the system (hardware-based) random number generator if it exists. 
     17if hasattr(random, 'SystemRandom'): 
     18    randint = random.SystemRandom().randint 
     19else: 
     20    randint = random.randint 
     21MAX_SESSION_KEY = 18446744073709551616L     # 2 << 63 
     22 
     23class CreateError(Exception): 
     24    """ 
     25    Used internally as a consistent exception type to catch from save (see the 
     26    docstring for SessionBase.save() for details). 
     27    """ 
     28    pass 
    1629 
    1730class SessionBase(object): 
     
    118131            pid = 1 
    119132        while 1: 
    120             session_key = md5_constructor("%s%s%s%s" % (random.randint(0, sys.maxint - 1), 
    121                                           pid, time.time(), settings.SECRET_KEY)).hexdigest() 
     133            session_key = md5_constructor("%s%s%s%s" 
     134                    % (random.randrange(0, MAX_SESSION_KEY), pid, time.time(), 
     135                       settings.SECRET_KEY)).hexdigest() 
    122136            if not self.exists(session_key): 
    123137                break 
     
    214228        raise NotImplementedError 
    215229 
    216     def save(self): 
    217         """ 
    218         Saves the session data. 
     230    def create(self): 
     231        """ 
     232        Creates a new session instance. Guaranteed to create a new object with 
     233        a unique key and will have saved the result once (with empty data) 
     234        before the method returns. 
     235        """ 
     236        raise NotImplementedError 
     237 
     238    def save(self, must_create=False): 
     239        """ 
     240        Saves the session data. If 'must_create' is True, a new session object 
     241        is created (otherwise a CreateError exception is raised). Otherwise, 
     242        save() can update an existing object with the same key. 
    219243        """ 
    220244        raise NotImplementedError 
  • django/trunk/django/contrib/sessions/backends/cache.py

    r8046 r8340  
    1 from django.contrib.sessions.backends.base import SessionBase 
     1from django.contrib.sessions.backends.base import SessionBase, CreateError 
    22from django.core.cache import cache 
    33 
     
    1212    def load(self): 
    1313        session_data = self._cache.get(self.session_key) 
    14         return session_data or {} 
     14        if session_data is not None: 
     15            return session_data 
     16        self.create() 
    1517 
    16     def save(self): 
    17         self._cache.set(self.session_key, self._session, self.get_expiry_age()) 
     18    def create(self): 
     19        while True: 
     20            self.session_key = self._get_new_session_key() 
     21            try: 
     22                self.save(must_create=True) 
     23            except CreateError: 
     24                continue 
     25            self.modified = True 
     26            return 
     27 
     28    def save(self, must_create=False): 
     29        if must_create: 
     30            func = self._cache.add 
     31        else: 
     32            func = self._cache.set 
     33        result = func(self.session_key, self._session, self.get_expiry_age()) 
     34        if must_create and not result: 
     35            raise CreateError 
    1836 
    1937    def exists(self, session_key): 
     
    2442    def delete(self, session_key): 
    2543        self._cache.delete(session_key) 
     44 
  • django/trunk/django/contrib/sessions/backends/db.py

    r8046 r8340  
    11import datetime 
    22from django.contrib.sessions.models import Session 
    3 from django.contrib.sessions.backends.base import SessionBase 
     3from django.contrib.sessions.backends.base import SessionBase, CreateError 
    44from django.core.exceptions import SuspiciousOperation 
     5from django.db import IntegrityError, transaction 
    56 
    67class SessionStore(SessionBase): 
     
    89    Implements database session store. 
    910    """ 
    10     def __init__(self, session_key=None): 
    11         super(SessionStore, self).__init__(session_key) 
    12  
    1311    def load(self): 
    1412        try: 
     
    1917            return self.decode(s.session_data) 
    2018        except (Session.DoesNotExist, SuspiciousOperation): 
    21  
    22             # Create a new session_key for extra security. 
    23             self.session_key = self._get_new_session_key() 
    24             self._session_cache = {} 
    25  
    26             # Save immediately to minimize collision 
    27             self.save() 
    28             # Ensure the user is notified via a new cookie. 
    29             self.modified = True 
     19            self.create() 
    3020            return {} 
    3121 
     
    3727        return True 
    3828 
    39     def save(self): 
    40         Session.objects.create( 
     29    def create(self): 
     30        while True: 
     31            self.session_key = self._get_new_session_key() 
     32            try: 
     33                # Save immediately to ensure we have a unique entry in the 
     34                # database. 
     35                self.save(must_create=True) 
     36            except CreateError: 
     37                # Key wasn't unique. Try again. 
     38                continue 
     39            self.modified = True 
     40            self._session_cache = {} 
     41            return 
     42 
     43    def save(self, must_create=False): 
     44        """ 
     45        Saves the current session data to the database. If 'must_create' is 
     46        True, a database error will be raised if the saving operation doesn't 
     47        create a *new* entry (as opposed to possibly updating an existing 
     48        entry). 
     49        """ 
     50        obj = Session( 
    4151            session_key = self.session_key, 
    4252            session_data = self.encode(self._session), 
    4353            expire_date = self.get_expiry_date() 
    4454        ) 
     55        sid = transaction.savepoint() 
     56        try: 
     57            obj.save(force_insert=must_create) 
     58        except IntegrityError: 
     59            if must_create: 
     60                transaction.savepoint_rollback(sid) 
     61                raise CreateError 
     62            raise 
    4563 
    4664    def delete(self, session_key): 
  • django/trunk/django/contrib/sessions/backends/file.py

    r7725 r8340  
    33 
    44from django.conf import settings 
    5 from django.contrib.sessions.backends.base import SessionBase 
     5from django.contrib.sessions.backends.base import SessionBase, CreateError 
    66from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured 
    77 
     
    4949                try: 
    5050                    session_data = self.decode(session_file.read()) 
    51                 except(EOFError, SuspiciousOperation): 
    52                     self._session_key = self._get_new_session_key() 
    53                     self._session_cache = {} 
    54                     self.save() 
    55                     # Ensure the user is notified via a new cookie. 
    56                     self.modified = True 
     51                except (EOFError, SuspiciousOperation): 
     52                    self.create() 
    5753            finally: 
    5854                session_file.close() 
    59         except(IOError)
     55        except IOError
    6056            pass 
    6157        return session_data 
    6258 
    63     def save(self): 
     59    def create(self): 
     60        while True: 
     61            self._session_key = self._get_new_session_key() 
     62            try: 
     63                self.save(must_create=True) 
     64            except CreateError: 
     65                continue 
     66            self.modified = True 
     67            self._session_cache = {} 
     68            return 
     69 
     70    def save(self, must_create=False): 
     71        flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | getattr(os, 'O_BINARY', 0) 
     72        if must_create: 
     73            flags |= os.O_EXCL 
    6474        try: 
    65             f = open(self._key_to_file(self.session_key), "wb"
     75            fd = os.open(self._key_to_file(self.session_key), flags
    6676            try: 
    67                 f.write(self.encode(self._session)) 
     77                os.write(fd, self.encode(self._session)) 
    6878            finally: 
    69                 f.close() 
    70         except(IOError, EOFError): 
     79                os.close(fd) 
     80        except OSError, e: 
     81            if must_create and e.errno == errno.EEXIST: 
     82                raise CreateError 
     83            raise 
     84        except (IOError, EOFError): 
    7185            pass 
    7286