sessionkey should not be case sensitive because some backends can store it on case insensitive file systems used internally as a consistent exception type to catch from save see the docstring for sessionbase save for details occurs if django tries to update a session that was deleted base class for all session classes valueerror unpickling exceptions if any of these happen just return an empty dictionary an empty session to avoid unnecessary persistent storage accesses we set up the internals directly loading data wastes time since we are going to set it to an empty dict anyway key must be truthy and at least 8 characters long 8 characters is an arbitrary lower bound for some minimal key security validate session key on assignment invalid values will set to none lazily load session from storage unless noload is true when only an empty dict is stored and store it in the current instance get the number of seconds until the session expires optionally this function accepts modification and expiry keyword arguments specifying the modification and expiry of the session make the difference between expirynone passed in kwargs and expiry not passed in kwargs in order to guarantee not to trigger self load when expiry is provided get session the expiry date as a datetime object optionally this function accepts modification and expiry keyword arguments specifying the modification and expiry of the session same comment as in getexpiryage set a custom expiration for the session value can be an integer a python datetime or timedelta object or none if value is an integer the session will expire after that many seconds of inactivity if set to 0 then the session will expire on browser close if value is a datetime or timedelta object the session will expire at that specific future time if value is none the session uses the global session expiry policy remove any custom expiration for this session return true if the session is set to expire when the browser closes and false if there s an expiry date use getexpirydate or getexpiryage to find the actual expiry dateage if there is one remove the current session data from the database and regenerate the key create a new session key while retaining the current session data methods that child classes must implement return true if the given sessionkey already exists create a new session instance guaranteed to create a new object with a unique key and will have saved the result once with empty data before the method returns save the session data if mustcreate is true create a new session object or raise createerror otherwise only update an existing object and don t create one raise updateerror if needed delete the session data under this key if the key is none use the current session key value load the session data and return a dictionary remove expired sessions from the session store if this operation isn t possible on a given backend it should raise notimplementederror if it isn t necessary because the backend has a builtin expiration mechanism it should be a noop session_key should not be case sensitive because some backends can store it on case insensitive file systems used internally as a consistent exception type to catch from save see the docstring for sessionbase save for details occurs if django tries to update a session that was deleted base class for all session classes valueerror unpickling exceptions if any of these happen just return an empty dictionary an empty session to avoid unnecessary persistent storage accesses we set up the internals directly loading data wastes time since we are going to set it to an empty dict anyway key must be truthy and at least 8 characters long 8 characters is an arbitrary lower bound for some minimal key security validate session key on assignment invalid values will set to none lazily load session from storage unless no_load is true when only an empty dict is stored and store it in the current instance get the number of seconds until the session expires optionally this function accepts modification and expiry keyword arguments specifying the modification and expiry of the session make the difference between expiry none passed in kwargs and expiry not passed in kwargs in order to guarantee not to trigger self load when expiry is provided checks both none and 0 cases get session the expiry date as a datetime object optionally this function accepts modification and expiry keyword arguments specifying the modification and expiry of the session same comment as in get_expiry_age set a custom expiration for the session value can be an integer a python datetime or timedelta object or none if value is an integer the session will expire after that many seconds of inactivity if set to 0 then the session will expire on browser close if value is a datetime or timedelta object the session will expire at that specific future time if value is none the session uses the global session expiry policy remove any custom expiration for this session return true if the session is set to expire when the browser closes and false if there s an expiry date use get_expiry_date or get_expiry_age to find the actual expiry date age if there is one remove the current session data from the database and regenerate the key create a new session key while retaining the current session data methods that child classes must implement return true if the given session_key already exists create a new session instance guaranteed to create a new object with a unique key and will have saved the result once with empty data before the method returns save the session data if must_create is true create a new session object or raise createerror otherwise only update an existing object and don t create one raise updateerror if needed delete the session data under this key if the key is none use the current session key value load the session data and return a dictionary remove expired sessions from the session store if this operation isn t possible on a given backend it should raise notimplementederror if it isn t necessary because the backend has a built in expiration mechanism it should be a no op
import logging
import string
from datetime import datetime, timedelta

from django.conf import settings
from django.core import signing
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.module_loading import import_string



VALID_KEY_CHARS = string.ascii_lowercase + string.digits


class CreateError(Exception):
    

    pass


class UpdateError(Exception):
    

    pass


class SessionBase:
    

    TEST_COOKIE_NAME = "testcookie"
    TEST_COOKIE_VALUE = "worked"

    __not_given = object()

    def __init__(self, session_key=None):
        self._session_key = session_key
        self.accessed = False
        self.modified = False
        self.serializer = import_string(settings.SESSION_SERIALIZER)

    def __contains__(self, key):
        return key in self._session

    def __getitem__(self, key):
        return self._session[key]

    def __setitem__(self, key, value):
        self._session[key] = value
        self.modified = True

    def __delitem__(self, key):
        del self._session[key]
        self.modified = True

    @property
    def key_salt(self):
        return "django.contrib.sessions." + self.__class__.__qualname__

    def get(self, key, default=None):
        return self._session.get(key, default)

    def pop(self, key, default=__not_given):
        self.modified = self.modified or key in self._session
        args = () if default is self.__not_given else (default,)
        return self._session.pop(key, *args)

    def setdefault(self, key, value):
        if key in self._session:
            return self._session[key]
        else:
            self.modified = True
            self._session[key] = value
            return value

    def set_test_cookie(self):
        self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE

    def test_cookie_worked(self):
        return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE

    def delete_test_cookie(self):
        del self[self.TEST_COOKIE_NAME]

    def encode(self, session_dict):
        "Return the given session dictionary serialized and encoded as a string."
        return signing.dumps(
            session_dict,
            salt=self.key_salt,
            serializer=self.serializer,
            compress=True,
        )

    def decode(self, session_data):
        try:
            return signing.loads(
                session_data, salt=self.key_salt, serializer=self.serializer
            )
        except signing.BadSignature:
            logger = logging.getLogger("django.security.SuspiciousSession")
            logger.warning("Session data corrupted")
        except Exception:
            
            
            pass
        return {}

    def update(self, dict_):
        self._session.update(dict_)
        self.modified = True

    def has_key(self, key):
        return key in self._session

    def keys(self):
        return self._session.keys()

    def values(self):
        return self._session.values()

    def items(self):
        return self._session.items()

    def clear(self):
        
        
        
        self._session_cache = {}
        self.accessed = True
        self.modified = True

    def is_empty(self):
        "Return True when there is no session_key and the session is empty."
        try:
            return not self._session_key and not self._session_cache
        except AttributeError:
            return True

    def _get_new_session_key(self):
        "Return session key that isn't being used."
        while True:
            session_key = get_random_string(32, VALID_KEY_CHARS)
            if not self.exists(session_key):
                return session_key

    def _get_or_create_session_key(self):
        if self._session_key is None:
            self._session_key = self._get_new_session_key()
        return self._session_key

    def _validate_session_key(self, key):
        
        return key and len(key) >= 8

    def _get_session_key(self):
        return self.__session_key

    def _set_session_key(self, value):
        
        if self._validate_session_key(value):
            self.__session_key = value
        else:
            self.__session_key = None

    session_key = property(_get_session_key)
    _session_key = property(_get_session_key, _set_session_key)

    def _get_session(self, no_load=False):
        
        self.accessed = True
        try:
            return self._session_cache
        except AttributeError:
            if self.session_key is None or no_load:
                self._session_cache = {}
            else:
                self._session_cache = self.load()
        return self._session_cache

    _session = property(_get_session)

    def get_session_cookie_age(self):
        return settings.SESSION_COOKIE_AGE

    def get_expiry_age(self, **kwargs):
        
        try:
            modification = kwargs["modification"]
        except KeyError:
            modification = timezone.now()
        
        
        
        try:
            expiry = kwargs["expiry"]
        except KeyError:
            expiry = self.get("_session_expiry")

        if not expiry:  
            return self.get_session_cookie_age()
        if not isinstance(expiry, (datetime, str)):
            return expiry
        if isinstance(expiry, str):
            expiry = datetime.fromisoformat(expiry)
        delta = expiry - modification
        return delta.days * 86400 + delta.seconds

    def get_expiry_date(self, **kwargs):
        
        try:
            modification = kwargs["modification"]
        except KeyError:
            modification = timezone.now()
        
        try:
            expiry = kwargs["expiry"]
        except KeyError:
            expiry = self.get("_session_expiry")

        if isinstance(expiry, datetime):
            return expiry
        elif isinstance(expiry, str):
            return datetime.fromisoformat(expiry)
        expiry = expiry or self.get_session_cookie_age()
        return modification + timedelta(seconds=expiry)

    def set_expiry(self, value):
        
        if value is None:
            
            try:
                del self["_session_expiry"]
            except KeyError:
                pass
            return
        if isinstance(value, timedelta):
            value = timezone.now() + value
        if isinstance(value, datetime):
            value = value.isoformat()
        self["_session_expiry"] = value

    def get_expire_at_browser_close(self):
        
        if (expiry := self.get("_session_expiry")) is None:
            return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
        return expiry == 0

    def flush(self):
        
        self.clear()
        self.delete()
        self._session_key = None

    def cycle_key(self):
        
        data = self._session
        key = self.session_key
        self.create()
        self._session_cache = data
        if key:
            self.delete(key)

    

    def exists(self, session_key):
        
        raise NotImplementedError(
            "subclasses of SessionBase must provide an exists() method"
        )

    def create(self):
        
        raise NotImplementedError(
            "subclasses of SessionBase must provide a create() method"
        )

    def save(self, must_create=False):
        
        raise NotImplementedError(
            "subclasses of SessionBase must provide a save() method"
        )

    def delete(self, session_key=None):
        
        raise NotImplementedError(
            "subclasses of SessionBase must provide a delete() method"
        )

    def load(self):
        
        raise NotImplementedError(
            "subclasses of SessionBase must provide a load() method"
        )

    @classmethod
    def clear_expired(cls):
        
        raise NotImplementedError("This backend does not support clear_expired().")