Pārlūkot izejas kodu

Fixed #20936 -- When logging out/ending a session, don't create a new, empty session.

Previously, when logging out, the existing session was overwritten by a
new sessionid instead of deleting the session altogether.

This behavior added overhead by creating a new session record in
whichever backend was in use: db, cache, etc.

This extra session is unnecessary at the time since no session data is
meant to be preserved when explicitly logging out.
Matt Robenolt 11 gadi atpakaļ
vecāks
revīzija
393c0e2422

+ 8 - 1
django/contrib/sessions/backends/base.py

@@ -142,6 +142,13 @@ class SessionBase(object):
         self.accessed = True
         self.modified = True
 
+    def is_empty(self):
+        "Returns True when there is no session_key and the session is empty"
+        try:
+            return not bool(self._session_key) and not self._session_cache
+        except AttributeError:
+            return True
+
     def _get_new_session_key(self):
         "Returns session key that isn't being used."
         while True:
@@ -268,7 +275,7 @@ class SessionBase(object):
         """
         self.clear()
         self.delete()
-        self.create()
+        self._session_key = None
 
     def cycle_key(self):
         """

+ 1 - 1
django/contrib/sessions/backends/cached_db.py

@@ -79,7 +79,7 @@ class SessionStore(DBStore):
         """
         self.clear()
         self.delete(self.session_key)
-        self.create()
+        self._session_key = ''
 
 
 # At bottom to avoid circular import

+ 28 - 21
django/contrib/sessions/middleware.py

@@ -18,32 +18,39 @@ class SessionMiddleware(object):
     def process_response(self, request, response):
         """
         If request.session was modified, or if the configuration is to save the
-        session every time, save the changes and set a session cookie.
+        session every time, save the changes and set a session cookie or delete
+        the session cookie if the session has been emptied.
         """
         try:
             accessed = request.session.accessed
             modified = request.session.modified
+            empty = request.session.is_empty()
         except AttributeError:
             pass
         else:
-            if accessed:
-                patch_vary_headers(response, ('Cookie',))
-            if modified or settings.SESSION_SAVE_EVERY_REQUEST:
-                if request.session.get_expire_at_browser_close():
-                    max_age = None
-                    expires = None
-                else:
-                    max_age = request.session.get_expiry_age()
-                    expires_time = time.time() + max_age
-                    expires = cookie_date(expires_time)
-                # Save the session data and refresh the client cookie.
-                # Skip session save for 500 responses, refs #3881.
-                if response.status_code != 500:
-                    request.session.save()
-                    response.set_cookie(settings.SESSION_COOKIE_NAME,
-                            request.session.session_key, max_age=max_age,
-                            expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
-                            path=settings.SESSION_COOKIE_PATH,
-                            secure=settings.SESSION_COOKIE_SECURE or None,
-                            httponly=settings.SESSION_COOKIE_HTTPONLY or None)
+            # First check if we need to delete this cookie.
+            # The session should be deleted only if the session is entirely empty
+            if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
+                response.delete_cookie(settings.SESSION_COOKIE_NAME)
+            else:
+                if accessed:
+                    patch_vary_headers(response, ('Cookie',))
+                if modified or settings.SESSION_SAVE_EVERY_REQUEST:
+                    if request.session.get_expire_at_browser_close():
+                        max_age = None
+                        expires = None
+                    else:
+                        max_age = request.session.get_expiry_age()
+                        expires_time = time.time() + max_age
+                        expires = cookie_date(expires_time)
+                    # Save the session data and refresh the client cookie.
+                    # Skip session save for 500 responses, refs #3881.
+                    if response.status_code != 500:
+                        request.session.save()
+                        response.set_cookie(settings.SESSION_COOKIE_NAME,
+                                request.session.session_key, max_age=max_age,
+                                expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
+                                path=settings.SESSION_COOKIE_PATH,
+                                secure=settings.SESSION_COOKIE_SECURE or None,
+                                httponly=settings.SESSION_COOKIE_HTTPONLY or None)
         return response

+ 21 - 0
django/contrib/sessions/tests.py

@@ -568,6 +568,27 @@ class SessionMiddlewareTests(unittest.TestCase):
         # Check that the value wasn't saved above.
         self.assertNotIn('hello', request.session.load())
 
+    def test_session_delete_on_end(self):
+        request = RequestFactory().get('/')
+        response = HttpResponse('Session test')
+        middleware = SessionMiddleware()
+
+        # Before deleting, there has to be an existing cookie
+        request.COOKIES[settings.SESSION_COOKIE_NAME] = 'abc'
+
+        # Simulate a request that ends the session
+        middleware.process_request(request)
+        request.session.flush()
+
+        # Handle the response through the middleware
+        response = middleware.process_response(request, response)
+
+        # Check that the cookie was deleted, not recreated.
+        # A deleted cookie header looks like:
+        #  Set-Cookie: sessionid=; expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/
+        self.assertEqual('Set-Cookie: {0}=; expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/'.format(settings.SESSION_COOKIE_NAME),
+            str(response.cookies[settings.SESSION_COOKIE_NAME]))
+
 
 class CookieSessionTests(SessionTestsMixin, TestCase):
 

+ 2 - 1
docs/releases/1.8.txt

@@ -61,7 +61,8 @@ Minor features
 :mod:`django.contrib.sessions`
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-* ...
+* Session cookie is now deleted after
+  :meth:`~django.contrib.sessions.backends.base.SessionBase.flush()` is called.
 
 :mod:`django.contrib.sitemaps`
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

+ 9 - 4
docs/topics/http/sessions.txt

@@ -231,12 +231,17 @@ You can edit it multiple times.
 
     .. method:: flush()
 
-      Delete the current session data from the session and regenerate the
-      session key value that is sent back to the user in the cookie. This is
-      used if you want to ensure that the previous session data can't be
-      accessed again from the user's browser (for example, the
+      Delete the current session data from the session and delete the session
+      cookie. This is used if you want to ensure that the previous session data
+      can't be accessed again from the user's browser (for example, the
       :func:`django.contrib.auth.logout()` function calls it).
 
+      .. versionchanged:: 1.8
+
+          Deletion of the session cookie is a behavior new in Django 1.8.
+          Previously, the behavior was to regenerate the session key value that
+          was sent back to the user in the cookie.
+
     .. method:: set_test_cookie()
 
       Sets a test cookie to determine whether the user's browser supports