Django

Code

Ticket #7515: clear_and_destroy.diff

File clear_and_destroy.diff, 8.5 kB (added by mrts, 4 months ago)

Exception-safe destroy() added to request.session, with tests and docs

  • django/contrib/sessions/middleware.py

    old new  
    2121        try: 
    2222            accessed = request.session.accessed 
    2323            modified = request.session.modified 
     24            destroyed = request.session.destroyed 
    2425        except AttributeError: 
     26            # request doesn't have 'session' attribute 
     27            # FIXME: is it such a good idea to hide this exception? 
     28            # if process_request() fails to set request.session we should 
     29            # raise here as well instead of silently failing, 
     30            # perhaps a SessionNotSetError? 
    2531            pass 
    2632        else: 
    2733            if accessed: 
    2834                patch_vary_headers(response, ('Cookie',)) 
    29             if modified or settings.SESSION_SAVE_EVERY_REQUEST: 
     35            if modified or destroyed or settings.SESSION_SAVE_EVERY_REQUEST: 
    3036                if request.session.get_expire_at_browser_close(): 
    3137                    max_age = None 
    3238                    expires = None 
     
    3541                    expires_time = time.time() + max_age 
    3642                    expires = cookie_date(expires_time) 
    3743                # Save the session data and refresh the client cookie. 
    38                 request.session.save() 
     44                needs_saving = modified or settings.SESSION_SAVE_EVERY_REQUEST 
     45                if destroyed and not needs_saving: 
     46                    # New session not modified after destruction. 
     47                    # Note that not saving here results in new key generation 
     48                    # in load() on the next request. If this is undesirable, 
     49                    # the three-branched needs_saving logic is not required. 
     50                    pass 
     51                elif destroyed and needs_saving: 
     52                    # New session modified after destruction.  We 
     53                    # need to guarantee no exception from the persistance 
     54                    # layer can block sending the new session cookie to 
     55                    # client. 
     56                    try: 
     57                        request.session.save() 
     58                    except: 
     59                        pass 
     60                else: 
     61                    request.session.save() 
    3962                response.set_cookie(settings.SESSION_COOKIE_NAME, 
    4063                        request.session.session_key, max_age=max_age, 
    4164                        expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, 
  • django/contrib/sessions/tests.py

    old new  
    2222>>> db_session.delete(db_session.session_key) 
    2323>>> db_session.exists(db_session.session_key) 
    2424False 
     25>>> db_session['foo'] = 'bar' 
     26>>> db_session.save() 
     27>>> db_session.exists(db_session.session_key) 
     28True 
     29>>> prev_key = db_session.session_key 
     30>>> db_session.save() 
     31>>> db_session.destroy() 
     32>>> db_session.exists(db_session.session_key) 
     33False 
     34>>> db_session.exists(prev_key) 
     35False 
     36>>> db_session.session_key == prev_key 
     37False 
     38>>> len(db_session.session_key) 
     3932 
     40>>> db_session.modified, db_session.accessed 
     41(False, True) 
     42>>> db_session['foo'] = 'bar' 
     43>>> db_session.modified, db_session.accessed 
     44(True, True) 
     45>>> db_session.save() 
     46>>> db_session.exists(db_session.session_key) 
     47True 
    2548 
    2649>>> file_session = FileSession() 
    2750>>> file_session.modified 
     
    3962>>> file_session.delete(file_session.session_key) 
    4063>>> file_session.exists(file_session.session_key) 
    4164False 
     65>>> file_session['foo'] = 'bar' 
     66>>> file_session.save() 
     67>>> file_session.exists(file_session.session_key) 
     68True 
     69>>> prev_key = file_session.session_key 
     70>>> file_session.save() 
     71>>> file_session.destroy() 
     72>>> file_session.exists(file_session.session_key) 
     73False 
     74>>> file_session.exists(prev_key) 
     75False 
     76>>> file_session.session_key == prev_key 
     77False 
     78>>> len(file_session.session_key) 
     7932 
     80>>> file_session.modified, file_session.accessed 
     81(False, True) 
     82>>> file_session['foo'] = 'bar' 
     83>>> file_session.modified, file_session.accessed 
     84(True, True) 
     85>>> file_session.save() 
     86>>> file_session.exists(file_session.session_key) 
     87True 
    4288 
    4389# Make sure the file backend checks for a good storage dir 
    4490>>> settings.SESSION_FILE_PATH = "/if/this/directory/exists/you/have/a/weird/computer" 
     
    61107>>> cache_session.delete(cache_session.session_key) 
    62108>>> cache_session.exists(cache_session.session_key) 
    63109False 
     110>>> cache_session['foo'] = 'bar' 
     111>>> cache_session.save() 
     112>>> cache_session.exists(cache_session.session_key) 
     113True 
     114>>> prev_key = cache_session.session_key 
     115>>> cache_session.save() 
     116>>> cache_session.destroy() 
     117>>> cache_session.exists(cache_session.session_key) 
     118False 
     119>>> cache_session.exists(prev_key) 
     120False 
     121>>> cache_session.session_key == prev_key 
     122False 
     123>>> len(cache_session.session_key) 
     12432 
     125>>> cache_session.modified, cache_session.accessed 
     126(False, True) 
     127>>> cache_session['foo'] = 'bar' 
     128>>> cache_session.modified, cache_session.accessed 
     129(True, True) 
     130>>> cache_session.save() 
     131>>> cache_session.exists(cache_session.session_key) 
     132True 
    64133 
    65134>>> s = SessionBase() 
    66135>>> s._session['some key'] = 'exists' # Pre-populate the session with some data 
     
    147216>>> list(i) 
    148217[('x', 1)] 
    149218 
    150   
     219# test .clear() 
     220>>> s.modified = s.accessed = False 
     221>>> s.items() 
     222[('x', 1)] 
     223>>> s.clear() 
     224>>> s.items() 
     225[] 
     226>>> s.accessed, s.modified 
     227(True, True) 
    151228 
     229 
    152230######################### 
    153231# Custom session expiry # 
    154232######################### 
  • django/contrib/sessions/backends/base.py

    old new  
    2525        self._session_key = session_key 
    2626        self.accessed = False 
    2727        self.modified = False 
     28        self.destroyed = False 
    2829 
    2930    def __contains__(self, key): 
    3031        return key in self._session 
     
    5354        self.modified = self.modified or key in self._session 
    5455        return self._session.pop(key, *args) 
    5556 
     57    def clear(self): 
     58        self._session.clear() 
     59        self.modified = True 
     60 
     61    def destroy(self): 
     62        self._session.clear() 
     63        try: 
     64            # remove old session, may fail 
     65            self.delete(self._session_key) 
     66        except: 
     67            # ignore exceptions to guarantee session key reset 
     68            pass 
     69        self._session_key = self._get_new_session_key(safe=True) 
     70        self.modified = False 
     71        self.accessed = True 
     72        self.destroyed = True 
     73 
    5674    def setdefault(self, key, value): 
    5775        if key in self._session: 
    5876            return self._session[key] 
     
    107125    def iteritems(self): 
    108126        return self._session.iteritems() 
    109127 
    110     def _get_new_session_key(self): 
     128    def _get_new_session_key(self, safe=False): 
    111129        "Returns session key that isn't being used." 
    112130        # The random module is seeded when this Apache child is created. 
    113131        # Use settings.SECRET_KEY as added salt. 
     
    119137        while 1: 
    120138            session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), 
    121139                                  pid, time.time(), settings.SECRET_KEY)).hexdigest() 
    122             if not self.exists(session_key): 
    123                 break 
     140            if safe: 
     141                try: 
     142                    if not self.exists(session_key): 
     143                        break 
     144                except: 
     145                    # Depends on the assumption that key collisions are rare, 
     146                    # see ticket #1180 
     147                    pass 
     148            else: 
     149                if not self.exists(session_key): 
     150                    break 
    124151        return session_key 
    125152 
    126153    def _get_session_key(self): 
  • docs/sessions.txt

    old new  
    106106 
    107107    * ``setdefault()`` (**New in Django development version**) 
    108108 
     109    * ``clear()`` (**New in Django development version**) 
     110 
    109111It also has these methods: 
    110112 
     113    * ``destroy()`` 
     114 
     115      **New in Django development version** 
     116 
     117      Clears session data, deletes the old session from session storage and 
     118      creates a new empty session in exception-safe manner. This is the 
     119      recommended, safe way of resetting sessions on e.g. user logout. 
     120 
    111121    * ``set_test_cookie()`` 
    112122 
    113123      Sets a test cookie to determine whether the user's browser supports