2

I just realized that my session doesn't expire when I use file-based session engine. Looking at Django code for file-based session, Django doesn't store any expiration information for a session, thus it's never expire unless the session file gets deleted manually.

This looks like a bug to me, as the database-backed session works fine, and I believe regardless of what session back-end developer chooses, they all should behave similarly.

Switching to database-backed session is not an option for me, as I need to store user's session in files.

Can anyone shed some lights? Is this really a bug? If yes, how do you suggest me to work around it?

Thanks!

Edwin
  • 803
  • 1
  • 11
  • 19

3 Answers3

3

So it looks like you're right. At least in django 1.4, using django.contrib.sessions.backends.file totally ignores SESSION_COOKIE_AGE. I'm not sure whether that's really a bug, or just undocumented.

If you really need this functionality, you can create your own session engine based on the file backend in contrib, but extend it with expiry functionality.

Open django/contrib/sessions/backends/file.py and add the following imports:

import datetime
from django.utils import timezone

Then, add two lines to the load method, so that it appears as below:

def load(self):
    session_data = {}
    try:
        session_file = open(self._key_to_file(), "rb")
        if (timezone.now() - datetime.datetime.fromtimestamp(os.path.getmtime(self._key_to_file()))).total_seconds() > settings.SESSION_COOKIE_AGE:
            raise IOError
        try:
            file_data = session_file.read()
            # Don't fail if there is no data in the session file.
            ....

This will actually compare the last modified date on the session file to expire it.

Save this file in your project somewhere and use it as your SESSION_ENGINE instead of 'django.contrib.sessions.backends.file'

You'll also need to enable SESSION_SAVE_EVERY_REQUEST in your settings if you want the session to timeout based on inactivity.

dgel
  • 16,352
  • 8
  • 58
  • 75
  • Yea I'm thinking about this route too as the last resort. – Edwin Apr 17 '12 at 21:48
  • I should probably ask at Django Users or Django developer mailing list, if this is indeed a bug – Edwin Apr 17 '12 at 21:49
  • Why does `SESSION_SAVE_EVERY_REQUEST` need to be enabled? Doesn't that defeat the purpose of expiring the session by setting a new modification time every time a request is made? – Edwin Apr 17 '12 at 21:56
  • 1
    Without the `SESSION_SAVE_EVERY_REQUEST`, the session will expire after `SESSION_COOKIE_AGE` seconds regardless of activity. If that's what you want, then don't bother with `SESSION_SAVE_EVERY_REQUEST`. If you want it to only expire after `SESSION_COOKIE_AGE` seconds of inactivity, then you want `SESSION_SAVE_EVERY_REQUEST` to be `True` – dgel Apr 17 '12 at 22:18
  • Oh ok got it. I do want to expire the session regardless of activity. Thanks for the quick response. Btw, can you update your answer to mention about this (the actual purpose of `SESSION_SAVE_EVERY_REQUEST`)? It might be helpful for others. – Edwin Apr 17 '12 at 22:53
  • You suggestion is excellent `you can create your own session engine based on the file backend in contrib, but extend it with expiry functionality`, but what you are actually doing is modifying the default session storage in your answer - which is not the right way to do it. Your fix is not upgrade friendly or portable. – Burhan Khalid Apr 18 '12 at 04:50
0

An option would be to use tmpwatch in the directory where you store the sessions

armonge
  • 3,108
  • 20
  • 36
  • I like this idea as it's simple to implement. However, it requires the system to have `tmpwatch`. – Edwin Apr 17 '12 at 21:43
0

I hit similar issue on Django 3.1. In my case, my program calls the function set_expiry(value) with an integer argument (int data type) before checking session expiry.

Accoring to Django documentation, the data type of argument value to set_expiry() can be int , datetime or timedelta. However for file-based session, expiry check inside load() doesn't work properly only if int argument is passed to set_expiry() beforehand, and such problem doesn't happen to datetime and timedelta argument of set_expiry().

The simple solution (workaround?) is to avoid int argument to set_expiry(value), you can do so by subclassing django.contrib.sessions.backends.file.SessionStore and overriding set_expiry(value) (code sample below), and change parameter SESSION_ENGINE accordingly in settings.py

from datetime import timedelta
from django.contrib.sessions.backends.file import SessionStore as FileSessionStore

class SessionStore(FileSessionStore):
    def set_expiry(self, value):
        """ force to convert to timedelta format """
        if value and isinstance(value, int):
            value = timedelta(seconds=value)
        super().set_expiry(value=value)

Note: It's also OK to pass timedelta or datetime to set_expiry(value) , but you will need to handle serialization issue on datetime object.

Ham
  • 703
  • 8
  • 17