Ticket #7515: clear_and_destroy.diff

File clear_and_destroy.diff, 8.5 KB (added by mrts, 7 years ago)

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

  • django/contrib/sessions/middleware.py

     
    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

     
    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

     
    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

     
    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
Back to Top