Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Most optimized way to delete all sessions for a specific user in Django?

I'm running Django 1.3, using Sessions Middleware and Auth Middleware:

# settings.py

SESSION_ENGINE = django.contrib.sessions.backends.db   # Persist sessions to DB
SESSION_COOKIE_AGE = 1209600                           # Cookies last 2 weeks

Each time a user logs in from a different location (different computer/browser), a new Session() is created and saved with a unique session_id. This can result in multiple database entries for the same user. Their login persists on that node until the cookie is deleted or session expires.

When a user changes their password, I want to delete all unexpired sessions for that user from the DB. That way after a password change, they're forced to re-login. This is for security purposes, such as if your computer got stolen, or you accidentally left yourself logged-in on a public terminal.

I want to know the best way to optimize this. Here's how I've done it:

# sessions_helpers.py

from django.contrib.sessions.models import Session
import datetime

def all_unexpired_sessions_for_user(user):
    user_sessions = []
    all_sessions  = Session.objects.filter(expire_date__gte=datetime.datetime.now())
    for session in all_sessions:
        session_data = session.get_decoded()
        if user.pk == session_data.get('_auth_user_id'):
            user_sessions.append(session)
    return user_sessions

def delete_all_unexpired_sessions_for_user(user, session_to_omit=None):
    for session in all_unexpired_sessions_for_user(user):
        if session is not session_to_omit:
            session.delete()

A very simplified view:

# views.py

from django.http import HttpResponse
from django.shortcuts import render_to_response
from myapp.forms import ChangePasswordForm
from sessions_helpers import delete_all_unexpired_sessions_for_user

@never_cache
@login_required
def change_password(request):
    user = request.user

    if request.method == 'POST':
        form = ChangePasswordForm(data=request)

        if form.is_valid():
            user.set_password(form.get('password'))
            user.save()
            request.session.cycle_key()         # Flushes and replaces old key. Prevents replay attacks.
            delete_all_unexpired_sessions_for_user(user=user, session_to_omit=request.session)
            return HttpResponse('Success!')

    else:
        form = ChangePasswordForm()

    return render_to_response('change_password.html', {'form':form}, context_instance=RequestContext(request))

As you can see in sessions_helpers.py, I have to pull every unexpired session out of the DB, Session.objects.filter(expire_date__gte=datetime.datetime.now()), decode all of them, and then check to see if it matches a user or not. This will be extremely costly to the database if there are, say, 100,000+ sessions stored in there.

Is there a more-database-friendly way to do this? Is there a Sessions/Auth Middleware setting that'll let you store the username as a column in the Sessions table so I can run SQL against that, or will I have to modify Sessions to do that? Out-of-the-box it only has session_key, session_data, and expire_date columns.

Thanks for any insight or help you can offer. :)

like image 695
Dave Avatar asked Jul 11 '11 21:07

Dave


People also ask

How do I clear a user session?

From the People page, find the user. Scroll down to click the user link. On the user's profile page, find the More Actions button (Note: this will not appear if you are logged on as the user whose profile page you have loaded). From the More Actions drop-down menu, choose Clear User Sessions, as shown below.

How do you delete a session in Django?

To delete a session or any particular key of that session, we can use del. The output will look like this and don't worry if your cookie didn't delete because we use this method only to delete your data in the Django database and not the session ID and cookie itself.

How do I delete all records in Django?

If you want to remove all the data from all your tables, you might want to try the command python manage.py flush . This will delete all of the data in your tables, but the tables themselves will still exist.

How does Django manage user sessions?

If you want to use a database-backed session, you need to add 'django. contrib. sessions' to your INSTALLED_APPS setting. Once you have configured your installation, run manage.py migrate to install the single database table that stores session data.


4 Answers

If you return a QuerySet from your all_unexpired_sessions_for_user function, you could limit your database hits to two:

def all_unexpired_sessions_for_user(user):
    user_sessions = []
    all_sessions  = Session.objects.filter(expire_date__gte=datetime.datetime.now())
    for session in all_sessions:
        session_data = session.get_decoded()
        if user.pk == session_data.get('_auth_user_id'):
            user_sessions.append(session.pk)
    return Session.objects.filter(pk__in=user_sessions)

def delete_all_unexpired_sessions_for_user(user, session_to_omit=None):
    session_list = all_unexpired_sessions_for_user(user)
    if session_to_omit is not None:
        session_list.exclude(session_key=session_to_omit.session_key)
    session_list.delete()

This gives you a total of two hits to the database. Once to loop over all of the Session objects, and once to delete all of the sessions. Unfortunately, I don't know of a more direct way to filter through the sessions themselves.

like image 131
Jack M. Avatar answered Oct 09 '22 16:10

Jack M.


Another version of a function using list comprehension that will just straight up delete every unexpired session of a user:

from django.utils import timezone
from django.contrib.sessions.models import Session


def delete_all_unexpired_sessions_for_user(user):
    unexpired_sessions = Session.objects.filter(expire_date__gte=timezone.now())
    [
        session.delete() for session in unexpired_sessions
        if str(user.pk) == session.get_decoded().get('_auth_user_id')
    ]
like image 28
Ed Patrick Tan Avatar answered Oct 09 '22 16:10

Ed Patrick Tan


The most efficient way is to store the session id of the user during login. You can access the session ID using request.session._session_key and store it in a separate model which has reference to the user. Now when you want to remove all the sessions of the user, just query this model which will return all the active sessions for the user in question. Now you need to delete only these sessions from the session table. Much better than having to look up all the sessions to filter out just sessions for a particular user.

like image 32
Pasada Avatar answered Oct 09 '22 16:10

Pasada


It might be helpful to use:

  • django-password-session in order to invalidate user's sessions after a password is changed. Since Django 1.7 this feature implemented out of the box.
  • django-admin clearsessions to remove expired cookies
like image 38
potar Avatar answered Oct 09 '22 17:10

potar