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.
This commit is contained in:
parent
9762ba2630
commit
393c0e2422
|
@ -142,6 +142,13 @@ class SessionBase(object):
|
||||||
self.accessed = True
|
self.accessed = True
|
||||||
self.modified = 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):
|
def _get_new_session_key(self):
|
||||||
"Returns session key that isn't being used."
|
"Returns session key that isn't being used."
|
||||||
while True:
|
while True:
|
||||||
|
@ -268,7 +275,7 @@ class SessionBase(object):
|
||||||
"""
|
"""
|
||||||
self.clear()
|
self.clear()
|
||||||
self.delete()
|
self.delete()
|
||||||
self.create()
|
self._session_key = None
|
||||||
|
|
||||||
def cycle_key(self):
|
def cycle_key(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -79,7 +79,7 @@ class SessionStore(DBStore):
|
||||||
"""
|
"""
|
||||||
self.clear()
|
self.clear()
|
||||||
self.delete(self.session_key)
|
self.delete(self.session_key)
|
||||||
self.create()
|
self._session_key = ''
|
||||||
|
|
||||||
|
|
||||||
# At bottom to avoid circular import
|
# At bottom to avoid circular import
|
||||||
|
|
|
@ -18,32 +18,39 @@ class SessionMiddleware(object):
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
"""
|
"""
|
||||||
If request.session was modified, or if the configuration is to save the
|
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:
|
try:
|
||||||
accessed = request.session.accessed
|
accessed = request.session.accessed
|
||||||
modified = request.session.modified
|
modified = request.session.modified
|
||||||
|
empty = request.session.is_empty()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if accessed:
|
# First check if we need to delete this cookie.
|
||||||
patch_vary_headers(response, ('Cookie',))
|
# The session should be deleted only if the session is entirely empty
|
||||||
if modified or settings.SESSION_SAVE_EVERY_REQUEST:
|
if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
|
||||||
if request.session.get_expire_at_browser_close():
|
response.delete_cookie(settings.SESSION_COOKIE_NAME)
|
||||||
max_age = None
|
else:
|
||||||
expires = None
|
if accessed:
|
||||||
else:
|
patch_vary_headers(response, ('Cookie',))
|
||||||
max_age = request.session.get_expiry_age()
|
if modified or settings.SESSION_SAVE_EVERY_REQUEST:
|
||||||
expires_time = time.time() + max_age
|
if request.session.get_expire_at_browser_close():
|
||||||
expires = cookie_date(expires_time)
|
max_age = None
|
||||||
# Save the session data and refresh the client cookie.
|
expires = None
|
||||||
# Skip session save for 500 responses, refs #3881.
|
else:
|
||||||
if response.status_code != 500:
|
max_age = request.session.get_expiry_age()
|
||||||
request.session.save()
|
expires_time = time.time() + max_age
|
||||||
response.set_cookie(settings.SESSION_COOKIE_NAME,
|
expires = cookie_date(expires_time)
|
||||||
request.session.session_key, max_age=max_age,
|
# Save the session data and refresh the client cookie.
|
||||||
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
|
# Skip session save for 500 responses, refs #3881.
|
||||||
path=settings.SESSION_COOKIE_PATH,
|
if response.status_code != 500:
|
||||||
secure=settings.SESSION_COOKIE_SECURE or None,
|
request.session.save()
|
||||||
httponly=settings.SESSION_COOKIE_HTTPONLY or None)
|
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
|
return response
|
||||||
|
|
|
@ -568,6 +568,27 @@ class SessionMiddlewareTests(unittest.TestCase):
|
||||||
# Check that the value wasn't saved above.
|
# Check that the value wasn't saved above.
|
||||||
self.assertNotIn('hello', request.session.load())
|
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):
|
class CookieSessionTests(SessionTestsMixin, TestCase):
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,8 @@ Minor features
|
||||||
:mod:`django.contrib.sessions`
|
:mod:`django.contrib.sessions`
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* ...
|
* Session cookie is now deleted after
|
||||||
|
:meth:`~django.contrib.sessions.backends.base.SessionBase.flush()` is called.
|
||||||
|
|
||||||
:mod:`django.contrib.sitemaps`
|
:mod:`django.contrib.sitemaps`
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -231,12 +231,17 @@ You can edit it multiple times.
|
||||||
|
|
||||||
.. method:: flush()
|
.. method:: flush()
|
||||||
|
|
||||||
Delete the current session data from the session and regenerate the
|
Delete the current session data from the session and delete the session
|
||||||
session key value that is sent back to the user in the cookie. This is
|
cookie. This is used if you want to ensure that the previous session data
|
||||||
used if you want to ensure that the previous session data can't be
|
can't be accessed again from the user's browser (for example, the
|
||||||
accessed again from the user's browser (for example, the
|
|
||||||
:func:`django.contrib.auth.logout()` function calls it).
|
: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()
|
.. method:: set_test_cookie()
|
||||||
|
|
||||||
Sets a test cookie to determine whether the user's browser supports
|
Sets a test cookie to determine whether the user's browser supports
|
||||||
|
|
Loading…
Reference in New Issue