In my django app under certain conditions I need to be able to force user logout by a username. Not necessarily the current user who is logged in, but some other user. So the request method in my view doesn’t have any session information about the user that I want to logout.
I am familiar with django.auth, and with auth.logout method, but it takes request as an argument. Is there a “django-way” to log user out if all I have is the username? Or do I have to roll my own logout sql?
I don’t think there is a sanctioned way to do this in Django yet.
The user id is stored in the session object, but it is encoded. Unfortunately, that means you’ll have to iterate through all sessions, decode and compare…
First delete the session objects for your target user. If they log in from multiple computers they will have multiple session objects.
from django.contrib.sessions.models import Session from django.contrib.auth.models import User # grab the user in question user = User.objects.get(username='johndoe') [s.delete() for s in Session.objects.all() if s.get_decoded().get('_auth_user_id') == user.id]
Then, if you need to, lock them out….
user.is_active = False user.save()
Although Harold’s answer works in this specific case, I can see at least two important issues with it:
- This solution can only be used with a database session engine. In other situations (cache, file, cookie) the
Sessionmodel would not be used.
- When the number of sessions and users in database grows, this becomes quite inefficient.
To solve those issues, I suggest you take another approach at the problem. The idea is to store somewhere the date when the user was logged in for a given session, and the last time you requested an user to be logged out.
Then whenever someone access your site, if the logged in date is lower than the log out date, you can force-logout the user. As dan said, there’s no practical difference between logging out an user immediately or on his next request to your site.
Now, let’s see a possible implementation of this solution, for django 1.3b1. In three steps:
1. store in the session the last login date
Fortunately, Django auth system exposes a signal called
user_logged_in. You just have to register that signals, and save the current date in the session. At the bottom of your
from django.contrib.auth.signals import user_logged_in from datetime import datetime def update_session_last_login(sender, user=user, request=request, **kwargs): if request: request.session['LAST_LOGIN_DATE'] = datetime.now() user_logged_in.connect(update_session_last_login)
2. request a force logout for an user
For the sake of simplicity, I’m gonna use model inheritance here, if you go for this solution, don’t forget to write a custom authentication backend.
from django.contrib.auth.models import User from django.db import models from datetime import datetime class MyUser(User): force_logout_date = models.DateTimeField(null=True, blank=True) def force_logout(self): self.force_logout_date = datetime.now() self.save()
Then, if you want to force logout for user
johndoe, you just have to:
from myapp.models import MyUser MyUser.objects.get(username='johndoe').force_logout()
3. implement the check on access
Best way here is to use a middleware as dan suggested. This middleware will access
request.user, so you need to put it after
'django.contrib.auth.middleware.AuthenticationMiddleware' in your
from django.contrib.auth import logout class ForceLogoutMiddleware(object): def process_request(self, request): if request.user.is_authenticated() and request.user.force_logout_date and \ request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date: logout(request)
That should do it.
- Be aware of the performance implication of storing an extra field for your users. Using model inheritance will add an extra
JOIN. Using user profiles will add an extra query. Modifying directly the
Useris the best way performance wise, but it is still a hairy topic.
If you deploy that solution on an existing site, you will probably have some trouble with existing sessions, which won’t have the
'LAST_LOGIN_DATE'key. You can adapt a bit the middleware code to deal with that case :
from django.contrib.auth import logout class ForceLogoutMiddleware(object): def process_request(self, request): if request.user.is_authenticated() and request.user.force_logout_date and \ ( 'LAST_LOGIN_DATE' not in request.session or \ request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date ): logout(request)
In django 1.2.x, there is no
user_logged_insignal. Fall back to overriding the
from django.contrib.auth import login as dj_login from datetime import datetime def login(request, user): dj_login(request, user) request.session['LAST_LOGIN_DATE'] = datetime.now()
I needed something similar in my app. In my case, if a user was set to inactive, I wanted to make sure if the user was already logged in that they will be logged out and not able to continue to use the site. After reading this post, I came to the following solution:
from django.contrib.auth import logout class ActiveUserMiddleware(object): def process_request(self, request): if not request.user.is_authenticated(): return if not request.user.is_active: logout(request)
Just add this middleware in your settings and off you go. In the case of changing passwords, you could introduce a new field in the userprofile model that forces a user to logout, check for the value of the field instead of is_active above, and also unset the field when a user logs in. The latter can be done with Django’s user_logged_in signal.
Perhaps, a bit of middleware that references a list of users who have been forced to log out. Next time the user tries to do anything, log them out then, redirects them, etc.
Unless of course, they need to be logged out immediately. But then again, they wouldn’t notice until they next tried to make a request anyway, so the above solution may just work.
This is in response to Balon’s query:
Yes, with around 140k sessions to iterate through I can see why Harold’s answer may not be as fast as you may like!
The way I would recommend is to add a model whose only two properties are foreign keys to
Session objects. Then add some middleware that keeps this model up-to-date with current user sessions. I have used this sort of setup before; in my case, I borrowed the
sessionprofile module from this Single Sign-On system for phpBB (see the source code in the “django/sessionprofile” folder) and this (I think) would suit your needs.
What you would end up with is some management function somewhere in your code like this (assuming the same code names and layout as in the
sessionprofile module linked above):
from sessionprofile.models import SessionProfile from django.contrib.auth.models import User # Find all SessionProfile objects corresponding to a given username sessionProfiles = SessionProfile.objects.filter(user__username__exact='johndoe') # Delete all corresponding sessions [sp.session.delete() for sp in sessionProfiles]
(I think this will also delete the
SessionProfile objects, as from what I recall, Django’s default behaviour when an object referenced by a
ForeignKey is deleted is to cascade it and also delete the object containing the
ForeignKey, but if not then it is trivial enough to delete the contents of
sessionProfiles when you are done.)
As Tony Abou-Assaleh, I also needed to log out users who were set to inactive, so I started by implementing his solution. After some time I found out that the middleware is forcing a DB query on all requests (to check if the user was blocked), and thus hurts performance on pages that doesn’t require login.
I have a custom user object and Django >= 1.7, so what I ended up doing is overriding its
get_session_auth_hash function to invalidate the session when the user is inactive. A possible implementation is:
def get_session_auth_hash(self): if not self.is_active: return "inactive" return super(MyCustomUser, self).get_session_auth_hash()
For this to work,
django.contrib.auth.middleware.SessionAuthenticationMiddleware should be in
Even I faced this issue. Few spammers from India keep posting about those Baba and Molvi for love solutions.
What I did is at the time of posting just inserted this code:
if request.user.is_active==False: return HttpResponse('You are banned on the site for spaming.')
from django.contrib.sessions.models import Session
deleting user session
[s.delete() for s in Session.objects.all() if s.get_decoded().get('_auth_user_hash') == user.get_session_auth_hash()]