Commit 5fec97b9 authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

Fixed #18194 -- Expiration of file-based sessions

* Prevented stale session files from being loaded
* Added removal of stale session files in django-admin.py clearsessions

Thanks ej for the report, crodjer and Elvard for their inputs.
parent 882c47cd
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
from __future__ import unicode_literals

import base64
import time
from datetime import datetime, timedelta
try:
    from django.utils.six.moves import cPickle as pickle
@@ -309,3 +308,14 @@ class SessionBase(object):
        Loads the session data and returns a dictionary.
        """
        raise NotImplementedError

    @classmethod
    def clear_expired(cls):
        """
        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.
        """
        raise NotImplementedError
+4 −0
Original line number Diff line number Diff line
@@ -65,3 +65,7 @@ class SessionStore(SessionBase):
                return
            session_key = self.session_key
        self._cache.delete(KEY_PREFIX + session_key)

    @classmethod
    def clear_expired(cls):
        pass
+5 −0
Original line number Diff line number Diff line
@@ -71,6 +71,11 @@ class SessionStore(SessionBase):
        except Session.DoesNotExist:
            pass

    @classmethod
    def clear_expired(cls):
        Session.objects.filter(expire_date__lt=timezone.now()).delete()
        transaction.commit_unless_managed()


# At bottom to avoid circular import
from django.contrib.sessions.models import Session
+59 −12
Original line number Diff line number Diff line
import datetime
import errno
import os
import tempfile
@@ -5,26 +6,35 @@ import tempfile
from django.conf import settings
from django.contrib.sessions.backends.base import SessionBase, CreateError
from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured

from django.utils import timezone

class SessionStore(SessionBase):
    """
    Implements a file based session store.
    """
    def __init__(self, session_key=None):
        self.storage_path = getattr(settings, "SESSION_FILE_PATH", None)
        if not self.storage_path:
            self.storage_path = tempfile.gettempdir()
        self.storage_path = type(self)._get_storage_path()
        self.file_prefix = settings.SESSION_COOKIE_NAME
        super(SessionStore, self).__init__(session_key)

    @classmethod
    def _get_storage_path(cls):
        try:
            return cls._storage_path
        except AttributeError:
            storage_path = getattr(settings, "SESSION_FILE_PATH", None)
            if not storage_path:
                storage_path = tempfile.gettempdir()

            # Make sure the storage path is valid.
        if not os.path.isdir(self.storage_path):
            if not os.path.isdir(storage_path):
                raise ImproperlyConfigured(
                    "The session storage path %r doesn't exist. Please set your"
                    " SESSION_FILE_PATH setting to an existing directory in which"
                " Django can store session data." % self.storage_path)
                    " Django can store session data." % storage_path)

        self.file_prefix = settings.SESSION_COOKIE_NAME
        super(SessionStore, self).__init__(session_key)
            cls._storage_path = storage_path
            return storage_path

    VALID_KEY_CHARS = set("abcdef0123456789")

@@ -44,6 +54,18 @@ class SessionStore(SessionBase):

        return os.path.join(self.storage_path, self.file_prefix + session_key)

    def _last_modification(self):
        """
        Return the modification time of the file storing the session's content.
        """
        modification = os.stat(self._key_to_file()).st_mtime
        if settings.USE_TZ:
            modification = datetime.datetime.utcfromtimestamp(modification)
            modification = modification.replace(tzinfo=timezone.utc)
        else:
            modification = datetime.datetime.fromtimestamp(modification)
        return modification

    def load(self):
        session_data = {}
        try:
@@ -56,6 +78,15 @@ class SessionStore(SessionBase):
                    session_data = self.decode(file_data)
                except (EOFError, SuspiciousOperation):
                    self.create()

                # Remove expired sessions.
                expiry_age = self.get_expiry_age(
                    modification=self._last_modification(),
                    expiry=session_data.get('_session_expiry'))
                if expiry_age < 0:
                    session_data = {}
                    self.delete()
                    self.create()
        except IOError:
            self.create()
        return session_data
@@ -142,3 +173,19 @@ class SessionStore(SessionBase):

    def clean(self):
        pass

    @classmethod
    def clear_expired(cls):
        storage_path = getattr(settings, "SESSION_FILE_PATH", tempfile.gettempdir())
        file_prefix = settings.SESSION_COOKIE_NAME

        for session_file in os.listdir(storage_path):
            if not session_file.startswith(file_prefix):
                continue
            session_key = session_file[len(file_prefix):]
            session = cls(session_key)
            # When an expired session is loaded, its file is removed, and a
            # new file is immediately created. Prevent this by disabling
            # the create() method.
            session.create = lambda: None
            session.load()
+4 −0
Original line number Diff line number Diff line
@@ -92,3 +92,7 @@ class SessionStore(SessionBase):
        return signing.dumps(session_cache, compress=True,
            salt='django.contrib.sessions.backends.signed_cookies',
            serializer=PickleSerializer)

    @classmethod
    def clear_expired(cls):
        pass
Loading