this does not inherit from testcase to avoid any tests being run with this class which wouldn t work and to allow different testcase subclasses to be used nb be careful to delete any sessions created stale sessions fill up the tmp with some backends and eventually overwhelm it after lots of runs think buildbots need to reset these to pretend we haven t accessed it submitting an invalid session key either by guessing or if the db has removed the key results in a new key being generated some backends leave a stale cache entry for the invalid session key make sure that entry is manually deleted falsey values such as an empty string are rejected self session sessionkey self assertisnoneself session sessionkey def testsessionkeytooshortinvalidself strings of length 8 and up are accepted and stored self session sessionkey 12345678 self assertequalself session sessionkey 12345678 def testsessionkeyisreadonlyself def setsessionkeysession session sessionkey session getnewsessionkey with self assertraisesattributeerror setsessionkeyself session custom session expiry def testdefaultexpiryself a normal session has a max age equal to settings self assertequalself session getexpiryage settings sessioncookieage so does a custom session with an idle expiration time of 0 but it ll expire at browser close self session setexpiry0 self assertequalself session getexpiryage settings sessioncookieage def testcustomexpirysecondsself modification timezone now self session setexpiry10 date self session getexpirydatemodificationmodification self assertequaldate modification timedeltaseconds10 age self session getexpiryagemodificationmodification self assertequalage 10 def testcustomexpirytimedeltaself modification timezone now mock timezone now because setexpiry calls it on this code path originalnow timezone now try timezone now lambda modification self session setexpirytimedeltaseconds10 finally timezone now originalnow date self session getexpirydatemodificationmodification self assertequaldate modification timedeltaseconds10 age self session getexpiryagemodificationmodification self assertequalage 10 def testcustomexpirydatetimeself modification timezone now self session setexpirymodification timedeltaseconds10 date self session getexpirydatemodificationmodification self assertequaldate modification timedeltaseconds10 age self session getexpiryagemodificationmodification self assertequalage 10 def testcustomexpiryresetself self session setexpirynone self session setexpiry10 self session setexpirynone self assertequalself session getexpiryage settings sessioncookieage def testgetexpireatbrowsercloseself tests getexpireatbrowserclose with different settings and different setexpiry calls with overridesettingssessionexpireatbrowserclosefalse self session setexpiry10 self assertisself session getexpireatbrowserclose false self session setexpiry0 self assertisself session getexpireatbrowserclose true self session setexpirynone self assertisself session getexpireatbrowserclose false with overridesettingssessionexpireatbrowserclosetrue self session setexpiry10 self assertisself session getexpireatbrowserclose false self session setexpiry0 self assertisself session getexpireatbrowserclose true self session setexpirynone self assertisself session getexpireatbrowserclose true def testdecodeself ensure we can decode what we encode data a test key a test value encoded self session encodedata self assertequalself session decodeencoded data def testdecodefailureloggedtosecurityself tests base64 b64encodebflaskdj alkdjf decodeascii bad encoded value for encoded in tests with self subtestencodedencoded with self assertlogs django security suspicioussession warning as cm self assertequalself session decodeencoded the failed decode is logged self assertinsession data corrupted cm output0 def testdecodeserializerexceptionself signer timestampsignersaltself session keysalt encoded signer signbinvalid data self assertequalself session decodeencoded def testactualexpiryself oldsessionkey none newsessionkey none try self sessionfoo bar self session setexpirytimedeltaseconds10 self session save oldsessionkey self session sessionkey with an expiry date in the past the session expires instantly newsession self backendself session sessionkey newsessionkey newsession sessionkey self assertnotinfoo newsession finally self session deleteoldsessionkey self session deletenewsessionkey def testsessionloaddoesnotcreaterecordself session self backendsomeunknownkey session load self assertisnonesession sessionkey self assertissession existssession sessionkey false provided unknown key was cycled not reused self assertnotequalsession sessionkey someunknownkey def testsessionsavedoesnotresurrectsessionloggedoutinothercontextself create new session s1 self backend s1testdata value1 s1 savemustcreatetrue logout in another context s2 self backends1 sessionkey s2 delete modify session in first context s1testdata value2 with self assertraisesupdateerror this should throw an exception as the session is deleted not resurrect the session s1 save self assertequals1 load class databasesessiontestssessiontestsmixin testcase backend databasesession sessionengine django contrib sessions backends db property def modelself return self backend getmodelclass def testsessionstrself session repr should be the session key self sessionx 1 self session save sessionkey self session sessionkey s self model objects getsessionkeysessionkey self assertequalstrs sessionkey def testsessiongetdecodedself self sessionx 1 self session save s self model objects getsessionkeyself session sessionkey self assertequals getdecoded x 1 def testsessionmanagersaveself create a session self sessiony 1 self session save s self model objects getsessionkeyself session sessionkey change it self model objects saves sessionkey y 2 s expiredate clear cache so that it will be retrieved from db del self session sessioncache self assertequalself sessiony 2 def testclearsessionscommandself self assertequal0 self model objects count one object in the future self sessionfoo bar self session setexpiry3600 self session save one object in the past othersession self backend othersessionfoo bar othersession setexpiry3600 othersession save two sessions are in the database before clearsessions self assertequal2 self model objects count with overridesettingssessionengineself sessionengine management callcommandclearsessions and one is deleted self assertequal1 self model objects count overridesettingsusetztrue class databasesessionwithtimezonetestsdatabasesessiontests pass class customdatabasesessiontestsdatabasesessiontests backend customdatabasesession sessionengine sessionstests models customsessioncookieage 60 60 24 one day def testextrasessionfieldself set the account id to be picked up by a custom session storage and saved to a custom session model database column self sessionauthuserid 42 self session save make sure that the customized createmodelinstance was called s self model objects getsessionkeyself session sessionkey self assertequals accountid 42 make the session anonymous self session popauthuserid self session save make sure that save on an existing session did the right job s self model objects getsessionkeyself session sessionkey self assertisnones accountid def testcustomexpiryresetself self session setexpirynone self session setexpiry10 self session setexpirynone self assertequalself session getexpiryage self customsessioncookieage def testdefaultexpiryself self assertequalself session getexpiryage self customsessioncookieage self session setexpiry0 self assertequalself session getexpiryage self customsessioncookieage class cachedbsessiontestssessiontestsmixin testcase backend cachedbsession def testexistssearchescachefirstself self session save with self assertnumqueries0 self assertisself session existsself session sessionkey true some backends might issue a warning ignorewarningsmoduledjango core cache backends base def testloadoverlongkeyself self session sessionkey string asciiletters string digits 20 self assertequalself session load overridesettingssessioncachealiassessions def testnondefaultcacheself 21000 cachedb backend should respect sessioncachealias with self assertraisesinvalidcachebackenderror self backend overridesettingsusetztrue class cachedbsessionwithtimezonetestscachedbsessiontests pass class filesessiontestssessiontestsmixin simpletestcase backend filesession def setupself do file session tests in an isolated directory and kill it after we re done self originalsessionfilepath settings sessionfilepath self tempsessionstore settings sessionfilepath self mkdtemp reset the file session backend s internal caches if hasattrself backend storagepath del self backend storagepath super setup def teardownself super teardown settings sessionfilepath self originalsessionfilepath shutil rmtreeself tempsessionstore def mkdtempself return tempfile mkdtemp overridesettings sessionfilepathifthisdirectoryexistsyouhaveaweirdcomputer def testconfigurationcheckself del self backend storagepath make sure the file backend checks for a good storage dir with self assertraisesimproperlyconfigured self backend def testinvalidkeybackslashself ensure we don t allow directorytraversal this is tested directly on keytofile as load will swallow a suspiciousoperation in the same way as an oserror by creating a new session making it unclear whether the slashes were detected with self assertraisesinvalidsessionkey self backend keytofileabc def testinvalidkeyforwardslashself ensure we don t allow directorytraversal with self assertraisesinvalidsessionkey self backend keytofileabc overridesettings sessionenginedjango contrib sessions backends file sessioncookieage0 def testclearsessionscommandself storagepath self backend getstoragepath fileprefix settings sessioncookiename def countsessions return len sessionfile for sessionfile in os listdirstoragepath if sessionfile startswithfileprefix self assertequal0 countsessions one object in the future self sessionfoo bar self session setexpiry3600 self session save one object in the past othersession self backend othersessionfoo bar othersession setexpiry3600 othersession save one object in the present without an expiry should be deleted since its modification time sessioncookieage will be in the past when clearsessions runs othersession2 self backend othersession2foo bar othersession2 save three sessions are in the filesystem before clearsessions self assertequal3 countsessions management callcommandclearsessions and two are deleted self assertequal1 countsessions class filesessionpathlibtestsfilesessiontests def mkdtempself tmpdir super mkdtemp return pathtmpdir class cachesessiontestssessiontestsmixin simpletestcase backend cachesession some backends might issue a warning ignorewarningsmoduledjango core cache backends base def testloadoverlongkeyself self session sessionkey string asciiletters string digits 20 self assertequalself session load def testdefaultcacheself self session save self assertisnotnonecachesdefault getself session cachekey overridesettings caches default backend django core cache backends dummy dummycache sessions backend django core cache backends locmem locmemcache location session sessioncachealiassessions def testnondefaultcacheself reinitialize the session backend to make use of overridden settings self session self backend self session save self assertisnonecachesdefault getself session cachekey self assertisnotnonecachessessions getself session cachekey def testcreateandsaveself self session self backend self session create self session save self assertisnotnonecachesdefault getself session cachekey class sessionmiddlewareteststestcase requestfactory requestfactory staticmethod def getresponsetouchingsessionrequest request sessionhello world return httpresponsesession test overridesettingssessioncookiesecuretrue def testsecuresessioncookieself request self requestfactory get middleware sessionmiddlewareself getresponsetouchingsession handle the response through the middleware response middlewarerequest self assertisresponse cookiessettings sessioncookienamesecure true overridesettingssessioncookiehttponlytrue def testhttponlysessioncookieself request self requestfactory get middleware sessionmiddlewareself getresponsetouchingsession handle the response through the middleware response middlewarerequest self assertisresponse cookiessettings sessioncookienamehttponly true self assertin cookies morsel reservedhttponly strresponse cookiessettings sessioncookiename overridesettingssessioncookiesamesitestrict def testsamesitesessioncookieself request self requestfactory get middleware sessionmiddlewareself getresponsetouchingsession response middlewarerequest self assertequal response cookiessettings sessioncookienamesamesite strict overridesettingssessioncookiehttponlyfalse def testnohttponlysessioncookieself request self requestfactory get middleware sessionmiddlewareself getresponsetouchingsession response middlewarerequest self assertequalresponse cookiessettings sessioncookienamehttponly self assertnotin cookies morsel reservedhttponly strresponse cookiessettings sessioncookiename def testsessionsaveon500self def response500request response httpresponsehorrible error response statuscode 500 request sessionhello world return response request self requestfactory get sessionmiddlewareresponse500request the value wasn t saved above self assertnotinhello request session load def testsessionsaveon5xxself def response503request response httpresponseservice unavailable response statuscode 503 request sessionhello world return response request self requestfactory get sessionmiddlewareresponse503request the value wasn t saved above self assertnotinhello request session load def testsessionupdateerrorredirectself def responsedeletesessionrequest request session databasesession request session savemustcreatetrue request session delete return httpresponse request self requestfactory getfoo middleware sessionmiddlewareresponsedeletesession msg the request s session was deleted before the request completed the user may have logged out in a concurrent request for example with self assertraisesmessagesessioninterrupted msg handle the response through the middleware it will try to save the deleted session which will cause an updateerror that s caught and raised as a sessioninterrupted middlewarerequest def testsessiondeleteonendself def responseendingsessionrequest request session flush return httpresponsesession test request self requestfactory get middleware sessionmiddlewareresponseendingsession before deleting there has to be an existing cookie request cookiessettings sessioncookiename abc handle the response through the middleware response middlewarerequest the cookie was deleted not recreated a deleted cookie header looks like setcookie sessionid expiresthu 01 jan 1970 00 00 00 gmt maxage0 path self assertequal setcookie expiresthu 01 jan 1970 00 00 00 gmt maxage0 path samesite format settings sessioncookiename settings sessioncookiesamesite strresponse cookiessettings sessioncookiename sessionmiddleware sets vary cookie to prevent the setcookie from being cached self assertequalresponse headersvary cookie overridesettings sessioncookiedomain example local sessioncookiepathexample def testsessiondeleteonendwithcustomdomainandpathself def responseendingsessionrequest request session flush return httpresponsesession test request self requestfactory get middleware sessionmiddlewareresponseendingsession before deleting there has to be an existing cookie request cookiessettings sessioncookiename abc handle the response through the middleware response middlewarerequest the cookie was deleted not recreated a deleted cookie header with a custom domain and path looks like setcookie sessionid domain example local expiresthu 01 jan 1970 00 00 00 gmt maxage0 pathexample self assertequal setcookie domain example local expiresthu 01 jan 1970 00 00 00 gmt maxage0 pathexample samesite format settings sessioncookiename settings sessioncookiesamesite strresponse cookiessettings sessioncookiename def testflushemptywithoutsessioncookiedoesntsetcookieself def responseendingsessionrequest request session flush return httpresponsesession test request self requestfactory get middleware sessionmiddlewareresponseendingsession handle the response through the middleware response middlewarerequest a cookie should not be set self assertequalresponse cookies the session is accessed so vary cookie should be set self assertequalresponse headersvary cookie def testemptysessionsavedself def responsesetsessionrequest set a session key and some data request sessionfoo bar return httpresponsesession test request self requestfactory get middleware sessionmiddlewareresponsesetsession handle the response through the middleware response middlewarerequest self assertequaltuplerequest session items foo bar a cookie should be set along with vary cookie self assertin setcookie sessionids request session sessionkey strresponse cookies self assertequalresponse headersvary cookie empty the session data del request sessionfoo handle the response through the middleware response httpresponsesession test response middleware processresponserequest response self assertequaldictrequest session values session session objects getsessionkeyrequest session sessionkey self assertequalsession getdecoded while the session is empty it hasn t been flushed so a cookie should still be set along with vary cookie self assertgreaterlenrequest session sessionkey 8 self assertin setcookie sessionids request session sessionkey strresponse cookies self assertequalresponse headersvary cookie class cookiesessiontestssessiontestsmixin simpletestcase backend cookiesession def testsaveself pass def testcycleself pass unittest expectedfailure def testactualexpiryself the cookie backend doesn t handle nondefault expiry dates see 19201 super testactualexpiry def testunpicklingexceptionself signedcookies backend should handle unpickle exceptions gracefully by creating a new session self assertequalself session serializer jsonserializer self session save with mock patchdjango core signing loads sideeffectvalueerror self session load unittest skip cookie backend doesn t have an external store to create records in def testsessionloaddoesnotcreaterecordself pass unittest skip cookiesession is stored in the client and there is no way to query it def testsessionsavedoesnotresurrectsessionloggedoutinothercontextself pass class clearsessionscommandtestssimpletestcase def testclearsessionsunsupportedself msg session engine sessionstests noclearexpired doesn t support clearing expired sessions with self settingssessionenginesessionstests noclearexpired with self assertraisesmessagemanagement commanderror msg management callcommandclearsessions this does not inherit from testcase to avoid any tests being run with this class which wouldn t work and to allow different testcase subclasses to be used subclasses must specify nb be careful to delete any sessions created stale sessions fill up the tmp with some backends and eventually overwhelm it after lots of runs think buildbots need to reset these to pretend we haven t accessed it submitting an invalid session key either by guessing or if the db has removed the key results in a new key being generated some backends leave a stale cache entry for the invalid session key make sure that entry is manually deleted falsey values such as an empty string are rejected strings shorter than 8 characters are rejected strings of length 8 and up are accepted and stored custom session expiry a normal session has a max age equal to settings so does a custom session with an idle expiration time of 0 but it ll expire at browser close mock timezone now because set_expiry calls it on this code path tests get_expire_at_browser_close with different settings and different set_expiry calls ensure we can decode what we encode the failed decode is logged with an expiry date in the past the session expires instantly loading an unknown session key does not create a session record creating session records on load is a dos vulnerability provided unknown key was cycled not reused sessions shouldn t be resurrected by a concurrent request create new session logout in another context modify session in first context this should throw an exception as the session is deleted not resurrect the session test we can use session get_decoded to retrieve data stored in normal way test sessionmanager save method create a session change it clear cache so that it will be retrieved from db test clearsessions command for clearing expired sessions one object in the future one object in the past two sessions are in the database before clearsessions and one is deleted one day set the account id to be picked up by a custom session storage and saved to a custom session model database column make sure that the customized create_model_instance was called make the session anonymous make sure that save on an existing session did the right job some backends might issue a warning 21000 cachedb backend should respect session_cache_alias do file session tests in an isolated directory and kill it after we re done reset the file session backend s internal caches make sure the file backend checks for a good storage dir ensure we don t allow directory traversal this is tested directly on _key_to_file as load will swallow a suspiciousoperation in the same way as an oserror by creating a new session making it unclear whether the slashes were detected ensure we don t allow directory traversal test clearsessions command for clearing expired sessions one object in the future one object in the past one object in the present without an expiry should be deleted since its modification time session_cookie_age will be in the past when clearsessions runs three sessions are in the filesystem before clearsessions and two are deleted some backends might issue a warning re initialize the session backend to make use of overridden settings handle the response through the middleware handle the response through the middleware the value wasn t saved above the value wasn t saved above handle the response through the middleware it will try to save the deleted session which will cause an updateerror that s caught and raised as a sessioninterrupted before deleting there has to be an existing cookie handle the response through the middleware 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 sessionmiddleware sets vary cookie to prevent the set cookie from being cached before deleting there has to be an existing cookie handle the response through the middleware the cookie was deleted not recreated a deleted cookie header with a custom domain and path looks like set cookie sessionid domain example local expires thu 01 jan 1970 00 00 00 gmt max age 0 path example handle the response through the middleware a cookie should not be set the session is accessed so vary cookie should be set if a session is emptied of data but still has a key it should still be updated set a session key and some data handle the response through the middleware a cookie should be set along with vary cookie empty the session data handle the response through the middleware while the session is empty it hasn t been flushed so a cookie should still be set along with vary cookie this test tested exists in the other session backends but that doesn t make sense for us this test tested cycle_key which would create a new session key for the same session data but we can t invalidate previously signed cookies other than letting them expire naturally so testing for this behavior is meaningless the cookie backend doesn t handle non default expiry dates see 19201 signed_cookies backend should handle unpickle exceptions gracefully by creating a new session
import base64
import os
import shutil
import string
import tempfile
import unittest
from datetime import timedelta
from http import cookies
from pathlib import Path
from unittest import mock

from django.conf import settings
from django.contrib.sessions.backends.base import UpdateError
from django.contrib.sessions.backends.cache import SessionStore as CacheSession
from django.contrib.sessions.backends.cached_db import SessionStore as CacheDBSession
from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
from django.contrib.sessions.backends.file import SessionStore as FileSession
from django.contrib.sessions.backends.signed_cookies import (
    SessionStore as CookieSession,
)
from django.contrib.sessions.exceptions import InvalidSessionKey, SessionInterrupted
from django.contrib.sessions.middleware import SessionMiddleware
from django.contrib.sessions.models import Session
from django.contrib.sessions.serializers import JSONSerializer
from django.core import management
from django.core.cache import caches
from django.core.cache.backends.base import InvalidCacheBackendError
from django.core.exceptions import ImproperlyConfigured
from django.core.signing import TimestampSigner
from django.http import HttpResponse
from django.test import (
    RequestFactory,
    SimpleTestCase,
    TestCase,
    ignore_warnings,
    override_settings,
)
from django.utils import timezone

from .models import SessionStore as CustomDatabaseSession


class SessionTestsMixin:
    
    
    

    backend = None  

    def setUp(self):
        self.session = self.backend()

    def tearDown(self):
        
        
        
        self.session.delete()

    def test_new_session(self):
        self.assertIs(self.session.modified, False)
        self.assertIs(self.session.accessed, False)

    def test_get_empty(self):
        self.assertIsNone(self.session.get("cat"))

    def test_store(self):
        self.session["cat"] = "dog"
        self.assertIs(self.session.modified, True)
        self.assertEqual(self.session.pop("cat"), "dog")

    def test_pop(self):
        self.session["some key"] = "exists"
        
        self.accessed = False
        self.modified = False

        self.assertEqual(self.session.pop("some key"), "exists")
        self.assertIs(self.session.accessed, True)
        self.assertIs(self.session.modified, True)
        self.assertIsNone(self.session.get("some key"))

    def test_pop_default(self):
        self.assertEqual(
            self.session.pop("some key", "does not exist"), "does not exist"
        )
        self.assertIs(self.session.accessed, True)
        self.assertIs(self.session.modified, False)

    def test_pop_default_named_argument(self):
        self.assertEqual(
            self.session.pop("some key", default="does not exist"), "does not exist"
        )
        self.assertIs(self.session.accessed, True)
        self.assertIs(self.session.modified, False)

    def test_pop_no_default_keyerror_raised(self):
        with self.assertRaises(KeyError):
            self.session.pop("some key")

    def test_setdefault(self):
        self.assertEqual(self.session.setdefault("foo", "bar"), "bar")
        self.assertEqual(self.session.setdefault("foo", "baz"), "bar")
        self.assertIs(self.session.accessed, True)
        self.assertIs(self.session.modified, True)

    def test_update(self):
        self.session.update({"update key": 1})
        self.assertIs(self.session.accessed, True)
        self.assertIs(self.session.modified, True)
        self.assertEqual(self.session.get("update key", None), 1)

    def test_has_key(self):
        self.session["some key"] = 1
        self.session.modified = False
        self.session.accessed = False
        self.assertIn("some key", self.session)
        self.assertIs(self.session.accessed, True)
        self.assertIs(self.session.modified, False)

    def test_values(self):
        self.assertEqual(list(self.session.values()), [])
        self.assertIs(self.session.accessed, True)
        self.session["some key"] = 1
        self.session.modified = False
        self.session.accessed = False
        self.assertEqual(list(self.session.values()), [1])
        self.assertIs(self.session.accessed, True)
        self.assertIs(self.session.modified, False)

    def test_keys(self):
        self.session["x"] = 1
        self.session.modified = False
        self.session.accessed = False
        self.assertEqual(list(self.session.keys()), ["x"])
        self.assertIs(self.session.accessed, True)
        self.assertIs(self.session.modified, False)

    def test_items(self):
        self.session["x"] = 1
        self.session.modified = False
        self.session.accessed = False
        self.assertEqual(list(self.session.items()), [("x", 1)])
        self.assertIs(self.session.accessed, True)
        self.assertIs(self.session.modified, False)

    def test_clear(self):
        self.session["x"] = 1
        self.session.modified = False
        self.session.accessed = False
        self.assertEqual(list(self.session.items()), [("x", 1)])
        self.session.clear()
        self.assertEqual(list(self.session.items()), [])
        self.assertIs(self.session.accessed, True)
        self.assertIs(self.session.modified, True)

    def test_save(self):
        self.session.save()
        self.assertIs(self.session.exists(self.session.session_key), True)

    def test_delete(self):
        self.session.save()
        self.session.delete(self.session.session_key)
        self.assertIs(self.session.exists(self.session.session_key), False)

    def test_flush(self):
        self.session["foo"] = "bar"
        self.session.save()
        prev_key = self.session.session_key
        self.session.flush()
        self.assertIs(self.session.exists(prev_key), False)
        self.assertNotEqual(self.session.session_key, prev_key)
        self.assertIsNone(self.session.session_key)
        self.assertIs(self.session.modified, True)
        self.assertIs(self.session.accessed, True)

    def test_cycle(self):
        self.session["a"], self.session["b"] = "c", "d"
        self.session.save()
        prev_key = self.session.session_key
        prev_data = list(self.session.items())
        self.session.cycle_key()
        self.assertIs(self.session.exists(prev_key), False)
        self.assertNotEqual(self.session.session_key, prev_key)
        self.assertEqual(list(self.session.items()), prev_data)

    def test_cycle_with_no_session_cache(self):
        self.session["a"], self.session["b"] = "c", "d"
        self.session.save()
        prev_data = self.session.items()
        self.session = self.backend(self.session.session_key)
        self.assertIs(hasattr(self.session, "_session_cache"), False)
        self.session.cycle_key()
        self.assertCountEqual(self.session.items(), prev_data)

    def test_save_doesnt_clear_data(self):
        self.session["a"] = "b"
        self.session.save()
        self.assertEqual(self.session["a"], "b")

    def test_invalid_key(self):
        
        
        try:
            session = self.backend("1")
            session.save()
            self.assertNotEqual(session.session_key, "1")
            self.assertIsNone(session.get("cat"))
            session.delete()
        finally:
            
            
            session.delete("1")

    def test_session_key_empty_string_invalid(self):
        
        self.session._session_key = ""
        self.assertIsNone(self.session.session_key)

    def test_session_key_too_short_invalid(self):
        
        self.session._session_key = "1234567"
        self.assertIsNone(self.session.session_key)

    def test_session_key_valid_string_saved(self):
        
        self.session._session_key = "12345678"
        self.assertEqual(self.session.session_key, "12345678")

    def test_session_key_is_read_only(self):
        def set_session_key(session):
            session.session_key = session._get_new_session_key()

        with self.assertRaises(AttributeError):
            set_session_key(self.session)

    
    def test_default_expiry(self):
        
        self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE)

        
        
        self.session.set_expiry(0)
        self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE)

    def test_custom_expiry_seconds(self):
        modification = timezone.now()

        self.session.set_expiry(10)

        date = self.session.get_expiry_date(modification=modification)
        self.assertEqual(date, modification + timedelta(seconds=10))

        age = self.session.get_expiry_age(modification=modification)
        self.assertEqual(age, 10)

    def test_custom_expiry_timedelta(self):
        modification = timezone.now()

        
        original_now = timezone.now
        try:
            timezone.now = lambda: modification
            self.session.set_expiry(timedelta(seconds=10))
        finally:
            timezone.now = original_now

        date = self.session.get_expiry_date(modification=modification)
        self.assertEqual(date, modification + timedelta(seconds=10))

        age = self.session.get_expiry_age(modification=modification)
        self.assertEqual(age, 10)

    def test_custom_expiry_datetime(self):
        modification = timezone.now()

        self.session.set_expiry(modification + timedelta(seconds=10))

        date = self.session.get_expiry_date(modification=modification)
        self.assertEqual(date, modification + timedelta(seconds=10))

        age = self.session.get_expiry_age(modification=modification)
        self.assertEqual(age, 10)

    def test_custom_expiry_reset(self):
        self.session.set_expiry(None)
        self.session.set_expiry(10)
        self.session.set_expiry(None)
        self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE)

    def test_get_expire_at_browser_close(self):
        
        
        with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=False):
            self.session.set_expiry(10)
            self.assertIs(self.session.get_expire_at_browser_close(), False)

            self.session.set_expiry(0)
            self.assertIs(self.session.get_expire_at_browser_close(), True)

            self.session.set_expiry(None)
            self.assertIs(self.session.get_expire_at_browser_close(), False)

        with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=True):
            self.session.set_expiry(10)
            self.assertIs(self.session.get_expire_at_browser_close(), False)

            self.session.set_expiry(0)
            self.assertIs(self.session.get_expire_at_browser_close(), True)

            self.session.set_expiry(None)
            self.assertIs(self.session.get_expire_at_browser_close(), True)

    def test_decode(self):
        
        data = {"a test key": "a test value"}
        encoded = self.session.encode(data)
        self.assertEqual(self.session.decode(encoded), data)

    def test_decode_failure_logged_to_security(self):
        tests = [
            base64.b64encode(b"flaskdj:alkdjf").decode("ascii"),
            "bad:encoded:value",
        ]
        for encoded in tests:
            with self.subTest(encoded=encoded):
                with self.assertLogs(
                    "django.security.SuspiciousSession", "WARNING"
                ) as cm:
                    self.assertEqual(self.session.decode(encoded), {})
                
                self.assertIn("Session data corrupted", cm.output[0])

    def test_decode_serializer_exception(self):
        signer = TimestampSigner(salt=self.session.key_salt)
        encoded = signer.sign(b"invalid data")
        self.assertEqual(self.session.decode(encoded), {})

    def test_actual_expiry(self):
        old_session_key = None
        new_session_key = None
        try:
            self.session["foo"] = "bar"
            self.session.set_expiry(-timedelta(seconds=10))
            self.session.save()
            old_session_key = self.session.session_key
            
            new_session = self.backend(self.session.session_key)
            new_session_key = new_session.session_key
            self.assertNotIn("foo", new_session)
        finally:
            self.session.delete(old_session_key)
            self.session.delete(new_session_key)

    def test_session_load_does_not_create_record(self):
        
        session = self.backend("someunknownkey")
        session.load()

        self.assertIsNone(session.session_key)
        self.assertIs(session.exists(session.session_key), False)
        
        self.assertNotEqual(session.session_key, "someunknownkey")

    def test_session_save_does_not_resurrect_session_logged_out_in_other_context(self):
        
        
        s1 = self.backend()
        s1["test_data"] = "value1"
        s1.save(must_create=True)

        
        s2 = self.backend(s1.session_key)
        s2.delete()

        
        s1["test_data"] = "value2"
        with self.assertRaises(UpdateError):
            
            
            s1.save()

        self.assertEqual(s1.load(), {})


class DatabaseSessionTests(SessionTestsMixin, TestCase):
    backend = DatabaseSession
    session_engine = "django.contrib.sessions.backends.db"

    @property
    def model(self):
        return self.backend.get_model_class()

    def test_session_str(self):
        "Session repr should be the session key."
        self.session["x"] = 1
        self.session.save()

        session_key = self.session.session_key
        s = self.model.objects.get(session_key=session_key)

        self.assertEqual(str(s), session_key)

    def test_session_get_decoded(self):
        
        self.session["x"] = 1
        self.session.save()

        s = self.model.objects.get(session_key=self.session.session_key)

        self.assertEqual(s.get_decoded(), {"x": 1})

    def test_sessionmanager_save(self):
        
        
        self.session["y"] = 1
        self.session.save()

        s = self.model.objects.get(session_key=self.session.session_key)
        
        self.model.objects.save(s.session_key, {"y": 2}, s.expire_date)
        
        del self.session._session_cache
        self.assertEqual(self.session["y"], 2)

    def test_clearsessions_command(self):
        
        self.assertEqual(0, self.model.objects.count())

        
        self.session["foo"] = "bar"
        self.session.set_expiry(3600)
        self.session.save()

        
        other_session = self.backend()
        other_session["foo"] = "bar"
        other_session.set_expiry(-3600)
        other_session.save()

        
        self.assertEqual(2, self.model.objects.count())
        with override_settings(SESSION_ENGINE=self.session_engine):
            management.call_command("clearsessions")
        
        self.assertEqual(1, self.model.objects.count())


@override_settings(USE_TZ=True)
class DatabaseSessionWithTimeZoneTests(DatabaseSessionTests):
    pass


class CustomDatabaseSessionTests(DatabaseSessionTests):
    backend = CustomDatabaseSession
    session_engine = "sessions_tests.models"
    custom_session_cookie_age = 60 * 60 * 24  

    def test_extra_session_field(self):
        
        
        self.session["_auth_user_id"] = 42
        self.session.save()

        
        s = self.model.objects.get(session_key=self.session.session_key)
        self.assertEqual(s.account_id, 42)

        
        self.session.pop("_auth_user_id")
        self.session.save()

        
        s = self.model.objects.get(session_key=self.session.session_key)
        self.assertIsNone(s.account_id)

    def test_custom_expiry_reset(self):
        self.session.set_expiry(None)
        self.session.set_expiry(10)
        self.session.set_expiry(None)
        self.assertEqual(self.session.get_expiry_age(), self.custom_session_cookie_age)

    def test_default_expiry(self):
        self.assertEqual(self.session.get_expiry_age(), self.custom_session_cookie_age)
        self.session.set_expiry(0)
        self.assertEqual(self.session.get_expiry_age(), self.custom_session_cookie_age)


class CacheDBSessionTests(SessionTestsMixin, TestCase):
    backend = CacheDBSession

    def test_exists_searches_cache_first(self):
        self.session.save()
        with self.assertNumQueries(0):
            self.assertIs(self.session.exists(self.session.session_key), True)

    
    @ignore_warnings(module="django.core.cache.backends.base")
    def test_load_overlong_key(self):
        self.session._session_key = (string.ascii_letters + string.digits) * 20
        self.assertEqual(self.session.load(), {})

    @override_settings(SESSION_CACHE_ALIAS="sessions")
    def test_non_default_cache(self):
        
        with self.assertRaises(InvalidCacheBackendError):
            self.backend()


@override_settings(USE_TZ=True)
class CacheDBSessionWithTimeZoneTests(CacheDBSessionTests):
    pass


class FileSessionTests(SessionTestsMixin, SimpleTestCase):
    backend = FileSession

    def setUp(self):
        
        self.original_session_file_path = settings.SESSION_FILE_PATH
        self.temp_session_store = settings.SESSION_FILE_PATH = self.mkdtemp()
        
        if hasattr(self.backend, "_storage_path"):
            del self.backend._storage_path
        super().setUp()

    def tearDown(self):
        super().tearDown()
        settings.SESSION_FILE_PATH = self.original_session_file_path
        shutil.rmtree(self.temp_session_store)

    def mkdtemp(self):
        return tempfile.mkdtemp()

    @override_settings(
        SESSION_FILE_PATH="/if/this/directory/exists/you/have/a/weird/computer",
    )
    def test_configuration_check(self):
        del self.backend._storage_path
        
        with self.assertRaises(ImproperlyConfigured):
            self.backend()

    def test_invalid_key_backslash(self):
        
        
        
        
        with self.assertRaises(InvalidSessionKey):
            self.backend()._key_to_file("a\\b\\c")

    def test_invalid_key_forwardslash(self):
        
        with self.assertRaises(InvalidSessionKey):
            self.backend()._key_to_file("a/b/c")

    @override_settings(
        SESSION_ENGINE="django.contrib.sessions.backends.file",
        SESSION_COOKIE_AGE=0,
    )
    def test_clearsessions_command(self):
        
        storage_path = self.backend._get_storage_path()
        file_prefix = settings.SESSION_COOKIE_NAME

        def count_sessions():
            return len(
                [
                    session_file
                    for session_file in os.listdir(storage_path)
                    if session_file.startswith(file_prefix)
                ]
            )

        self.assertEqual(0, count_sessions())

        
        self.session["foo"] = "bar"
        self.session.set_expiry(3600)
        self.session.save()

        
        other_session = self.backend()
        other_session["foo"] = "bar"
        other_session.set_expiry(-3600)
        other_session.save()

        
        
        
        other_session2 = self.backend()
        other_session2["foo"] = "bar"
        other_session2.save()

        
        self.assertEqual(3, count_sessions())
        management.call_command("clearsessions")
        
        self.assertEqual(1, count_sessions())


class FileSessionPathLibTests(FileSessionTests):
    def mkdtemp(self):
        tmp_dir = super().mkdtemp()
        return Path(tmp_dir)


class CacheSessionTests(SessionTestsMixin, SimpleTestCase):
    backend = CacheSession

    
    @ignore_warnings(module="django.core.cache.backends.base")
    def test_load_overlong_key(self):
        self.session._session_key = (string.ascii_letters + string.digits) * 20
        self.assertEqual(self.session.load(), {})

    def test_default_cache(self):
        self.session.save()
        self.assertIsNotNone(caches["default"].get(self.session.cache_key))

    @override_settings(
        CACHES={
            "default": {
                "BACKEND": "django.core.cache.backends.dummy.DummyCache",
            },
            "sessions": {
                "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
                "LOCATION": "session",
            },
        },
        SESSION_CACHE_ALIAS="sessions",
    )
    def test_non_default_cache(self):
        
        self.session = self.backend()

        self.session.save()
        self.assertIsNone(caches["default"].get(self.session.cache_key))
        self.assertIsNotNone(caches["sessions"].get(self.session.cache_key))

    def test_create_and_save(self):
        self.session = self.backend()
        self.session.create()
        self.session.save()
        self.assertIsNotNone(caches["default"].get(self.session.cache_key))


class SessionMiddlewareTests(TestCase):
    request_factory = RequestFactory()

    @staticmethod
    def get_response_touching_session(request):
        request.session["hello"] = "world"
        return HttpResponse("Session test")

    @override_settings(SESSION_COOKIE_SECURE=True)
    def test_secure_session_cookie(self):
        request = self.request_factory.get("/")
        middleware = SessionMiddleware(self.get_response_touching_session)

        
        response = middleware(request)
        self.assertIs(response.cookies[settings.SESSION_COOKIE_NAME]["secure"], True)

    @override_settings(SESSION_COOKIE_HTTPONLY=True)
    def test_httponly_session_cookie(self):
        request = self.request_factory.get("/")
        middleware = SessionMiddleware(self.get_response_touching_session)

        
        response = middleware(request)
        self.assertIs(response.cookies[settings.SESSION_COOKIE_NAME]["httponly"], True)
        self.assertIn(
            cookies.Morsel._reserved["httponly"],
            str(response.cookies[settings.SESSION_COOKIE_NAME]),
        )

    @override_settings(SESSION_COOKIE_SAMESITE="Strict")
    def test_samesite_session_cookie(self):
        request = self.request_factory.get("/")
        middleware = SessionMiddleware(self.get_response_touching_session)
        response = middleware(request)
        self.assertEqual(
            response.cookies[settings.SESSION_COOKIE_NAME]["samesite"], "Strict"
        )

    @override_settings(SESSION_COOKIE_HTTPONLY=False)
    def test_no_httponly_session_cookie(self):
        request = self.request_factory.get("/")
        middleware = SessionMiddleware(self.get_response_touching_session)
        response = middleware(request)
        self.assertEqual(response.cookies[settings.SESSION_COOKIE_NAME]["httponly"], "")
        self.assertNotIn(
            cookies.Morsel._reserved["httponly"],
            str(response.cookies[settings.SESSION_COOKIE_NAME]),
        )

    def test_session_save_on_500(self):
        def response_500(request):
            response = HttpResponse("Horrible error")
            response.status_code = 500
            request.session["hello"] = "world"
            return response

        request = self.request_factory.get("/")
        SessionMiddleware(response_500)(request)

        
        self.assertNotIn("hello", request.session.load())

    def test_session_save_on_5xx(self):
        def response_503(request):
            response = HttpResponse("Service Unavailable")
            response.status_code = 503
            request.session["hello"] = "world"
            return response

        request = self.request_factory.get("/")
        SessionMiddleware(response_503)(request)

        
        self.assertNotIn("hello", request.session.load())

    def test_session_update_error_redirect(self):
        def response_delete_session(request):
            request.session = DatabaseSession()
            request.session.save(must_create=True)
            request.session.delete()
            return HttpResponse()

        request = self.request_factory.get("/foo/")
        middleware = SessionMiddleware(response_delete_session)

        msg = (
            "The request's session was deleted before the request completed. "
            "The user may have logged out in a concurrent request, for example."
        )
        with self.assertRaisesMessage(SessionInterrupted, msg):
            
            
            
            middleware(request)

    def test_session_delete_on_end(self):
        def response_ending_session(request):
            request.session.flush()
            return HttpResponse("Session test")

        request = self.request_factory.get("/")
        middleware = SessionMiddleware(response_ending_session)

        
        request.COOKIES[settings.SESSION_COOKIE_NAME] = "abc"

        
        response = middleware(request)

        
        
        
        
        self.assertEqual(
            'Set-Cookie: {}=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; '
            "Max-Age=0; Path=/; SameSite={}".format(
                settings.SESSION_COOKIE_NAME,
                settings.SESSION_COOKIE_SAMESITE,
            ),
            str(response.cookies[settings.SESSION_COOKIE_NAME]),
        )
        
        
        self.assertEqual(response.headers["Vary"], "Cookie")

    @override_settings(
        SESSION_COOKIE_DOMAIN=".example.local", SESSION_COOKIE_PATH="/example/"
    )
    def test_session_delete_on_end_with_custom_domain_and_path(self):
        def response_ending_session(request):
            request.session.flush()
            return HttpResponse("Session test")

        request = self.request_factory.get("/")
        middleware = SessionMiddleware(response_ending_session)

        
        request.COOKIES[settings.SESSION_COOKIE_NAME] = "abc"

        
        response = middleware(request)

        
        
        
        
        
        self.assertEqual(
            'Set-Cookie: {}=""; Domain=.example.local; expires=Thu, '
            "01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/example/; SameSite={}".format(
                settings.SESSION_COOKIE_NAME,
                settings.SESSION_COOKIE_SAMESITE,
            ),
            str(response.cookies[settings.SESSION_COOKIE_NAME]),
        )

    def test_flush_empty_without_session_cookie_doesnt_set_cookie(self):
        def response_ending_session(request):
            request.session.flush()
            return HttpResponse("Session test")

        request = self.request_factory.get("/")
        middleware = SessionMiddleware(response_ending_session)

        
        response = middleware(request)

        
        self.assertEqual(response.cookies, {})
        
        self.assertEqual(response.headers["Vary"], "Cookie")

    def test_empty_session_saved(self):
        

        def response_set_session(request):
            
            request.session["foo"] = "bar"
            return HttpResponse("Session test")

        request = self.request_factory.get("/")
        middleware = SessionMiddleware(response_set_session)

        
        response = middleware(request)
        self.assertEqual(tuple(request.session.items()), (("foo", "bar"),))
        
        self.assertIn(
            "Set-Cookie: sessionid=%s" % request.session.session_key,
            str(response.cookies),
        )
        self.assertEqual(response.headers["Vary"], "Cookie")

        
        del request.session["foo"]
        
        response = HttpResponse("Session test")
        response = middleware.process_response(request, response)
        self.assertEqual(dict(request.session.values()), {})
        session = Session.objects.get(session_key=request.session.session_key)
        self.assertEqual(session.get_decoded(), {})
        
        
        self.assertGreater(len(request.session.session_key), 8)
        self.assertIn(
            "Set-Cookie: sessionid=%s" % request.session.session_key,
            str(response.cookies),
        )
        self.assertEqual(response.headers["Vary"], "Cookie")


class CookieSessionTests(SessionTestsMixin, SimpleTestCase):
    backend = CookieSession

    def test_save(self):
        
        pass

    def test_cycle(self):
        
        pass

    @unittest.expectedFailure
    def test_actual_expiry(self):
        
        super().test_actual_expiry()

    def test_unpickling_exception(self):
        
        
        self.assertEqual(self.session.serializer, JSONSerializer)
        self.session.save()
        with mock.patch("django.core.signing.loads", side_effect=ValueError):
            self.session.load()

    @unittest.skip(
        "Cookie backend doesn't have an external store to create records in."
    )
    def test_session_load_does_not_create_record(self):
        pass

    @unittest.skip(
        "CookieSession is stored in the client and there is no way to query it."
    )
    def test_session_save_does_not_resurrect_session_logged_out_in_other_context(self):
        pass


class ClearSessionsCommandTests(SimpleTestCase):
    def test_clearsessions_unsupported(self):
        msg = (
            "Session engine 'sessions_tests.no_clear_expired' doesn't "
            "support clearing expired sessions."
        )
        with self.settings(SESSION_ENGINE="sessions_tests.no_clear_expired"):
            with self.assertRaisesMessage(management.CommandError, msg):
                management.call_command("clearsessions")