How to force a user logout in Django? - django

In my Django app under certain conditions I want to be able to force users to log out by a username. Not necessarily the current user who is logged in, but another 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 the user out if all I have is the username? Or do I have to roll my own logout SQL?

Update:
Since Django 1.7, users are automatically logged-out when their password changes. On each request, the current password hash is compared to the value saved in their session and if doesn't match, the user is logged-out.
So, a simple password update has the effect of logging the user out. You can then disable the account for login, or advise them to use the password reset feature to set a new password and log in again.
Original:
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...
Two steps:
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 Session model 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 a 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 a 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 models.py :
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 a user
We just need to add a field and a method to the User model. There's multiple ways to achieve that (user profiles, model inheritance, etc.) each with pros and cons.
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 MIDDLEWARE_CLASSES setting.
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.
Notes
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 User is 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_in signal. Fall back to overriding the login function:
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 User and 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.)

You can also use direct django function to do that, it will update and logs out all other sessions for the user, except the current one.
from django.contrib.auth import update_session_auth_hash
update_session_auth_hash(self.request, user)
Docs for update_session_auth_hash here.

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 settings.MIDDLEWARE_CLASSES

As others stated, you can iterate over all sessions in DB, decode all of them, and delete those belonging to that user. But it's slow, particularly if your site has high traffic and there are lots of sessions.
If you need a faster solution, you can use a session backend that lets you query and get the sessions of a specific user. In these session backends, Session has a foreign key to User, so you don't need to iterate over all session objects:
django-qsessions (based on django's db, cached_db session backends)
django-user-sessions (based on django's db session backend)
Using these backends, deleting all sessions of a user can be done in a single line of code:
user.session_set.all().delete()
Disclaimer: I am the author of django-qsessions.

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()]

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.')

Related

Separate admin site for users

I'm kinda new to Django and looking for some advice on a medium sized project. Our in-office admin is one thing, but our users can also log in online and see a similar (more limited, but mostly the same) interface.
The problem is that the users email is the username, and emails don't have to be unique (but passwords must for reasons you understand). Ghetto? Yes. But it's what they expect.
Is it possible to create this second interface as well as deviate away from the traditional username for sign in? Does Django 1.5s custom user model do anything for me here? And is it possible to make use of two user models--one for the admin, and one for our users?
And finally, if I'm heading in the wrong direction entirely, please let me know. This is a follow-up to a question I'd asked previously, but I'd forgotten about some of the details.
EDIT: Much as I hate to do it, I'll just code the member area seperately. More weirdness with the logins has emerged so I think this will be the easiest solution, even if not very DRY.
Having two admin interfaces is probably silly. Django's admin supports robust user permissions.
Keep username's unique, and use an email authentication backend. This lets users user their email address if they want to. Since, at least in 1.4, email addresses didn't have to be unique, you're in the clear.
Here's a simple email auth backend:
from django.contrib.auth.backends import ModelBackend
from django.contrib.admin.models import User
class EmailAuthBackEnd(ModelBackend):
def authenticate(self, email=None, password=None,**kwargs):
try:
user = User.objects.get(email=email)
if user.check_password(password):
return user
return None
except User.DoesNotExist:
return None
Add the above to a file called backends.py in one of your apps, then add this to your settings.py:
AUTHENTICATION_BACKENDS = AUTHENTICATION_BACKENDS + ('app.backends.EmailAuthBackEnd',)
On the whole unique passwords thing … that's tricky to deal with. You could subclass Django's user model, and change password to a unique string, or you could patch PSQL manually to enforce that constraint. OR you could be lazy, assume passwords will be assigned, assign every user a random username, safe in the knowledge you can always login and 'fix' problem accounts.
I would suggest that you allow users to retain their current credentials (that will be a big plus from a usability point of view), and then adjust what they see depending on where they're coming from. There's lots of methods on django's ModelAdmin that you can override. So on a model by model basis you could do -
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
self.exclude = []
if request.META(REMOTE_ADDR) not in [# add list of internal ip's here]
self.exclude.append('field_to_hide')
return super(MyModelAdmin, self).get_form(request, obj, **kwargs)
As in the docs example here. (also have a look at the list of http headers here to get an idea of how you might make your check)
If you want to change the list of objects available on the main admin menu then, look at overriding the AdminSite, to add your own get_urls() method that provides similar functionality to the individual get_form method I've suggested above.

Language stored in Useprofile.language field applied on user log in

I need to set language for user in 2 cases:
User logs in (django checks his UserProfile field for language field value and sets the proper lang)
Users chenges lang in "user preferences" page.
I've tried with "user_logged_in" signal receiver
from django.utils import translation
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_in
#receiver(user_logged_in)
def setlang(sender, **kwargs):
translation.activate(kwargs['user'].get_profile().language)
kwargs['request'].session['django_language'] = translation.get_language()
This works fine until I restart my django instance. Though session is alive (no need to log in again) website is being displayed in language specified in settings.LANGUAGE
I think the same applies to situation #2 (on user preferences form save())
Following up to Timmy's answer you can find an already published middleware that does the job for you!
The translation of a Django app with the preferred language selected by a registered user can be done with the middleware django-user-language-middleware. This allows easy translation of your Django app by looking at the selected language in the user.language field.
Usage:
Add a language field to your user model:
class User(auth_base.AbstractBaseUser, auth.PermissionsMixin):
# ...
language = models.CharField(max_length=10,
choices=settings.LANGUAGES,
default=settings.LANGUAGE_CODE)
Install the middleware from pip:
pip install django-user-language-middleware
Add it to your middleware class list in settings to listen to requests:
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10
...
'user_language_middleware.UserLanguageMiddleware',
...
]
I hope this may help people landing on this question in the future.
I think it's better if you use middleware to achieve this. Django provides a way to add in hooks at various places throughout a request so that you can add custom code.
In your case: check on every request if there is a language variable in the session, if not, fetch the users language preference from the database and save it to the session variable. Check on a save signal whether or not the user has changed their preference; if so, update the session variable (you might have problems getting access to the request from the model level so it might be better to do this in the view).
I've used this snippet, but it conflicts with the Regular LocaleMiddleware, and thus I can't add a multilingual content farm.
http://django-hotclub.googlecode.com/svn-history/r708/trunk/pinax/profiles/middleware.py

Django authentication problem - how can I force active users to setup a profile first?

I've put myself into somewhat of a pickle. I use django-registration often, and it seems to work for most situations. However, I want to require users to build their profile (eg: demographic information) before they can visit any of the other pages.
This is how I desire the current setup to run:
visitor fills out registration form --(submit)--->
user email verification --(link creates active user)--->
--(redirected to profile view)--->
user fills out profile form --(submit)-->
user can now access the rest of the website
Is there a recommended way to do this?
One of the ways of doing it would be to use your own #profile_required decorator rather than the django's built in login_required on all your views.
#login_required
def profile_required(func,request,*args,**kwargs):
has_profile = request.user.profile_set.count()
if not has_profile:
return redirect('create_profile')
return func(request,*args,**kwargs)
Then on each view you want to have a user with profile visit, just:
#profile_required
def my_awesome_view(request):
...

Anyone think django's user model is too tightly coupled with auth?

I'm trying to learn Django and I would like feedback from anyone who has any MVC/MTV/PHP/Ruby framework experience. Does anyone find that the user model is too tightly coupled with auth?
Background: When you first implement authentication for Django, you include the module django.contrib.auth
This will bring in several models like User, Group, Message etc. Let's focus on the User model as this is the one of the most important tables in any website.
In short the User table has these fields
User
username max_length 30, unique, [letters, digits, underscores]
password max_length 75
email max_length 75
...and about 8 other useful fields like first_name, last_name, etc.
Goal:
I want to remove username and use email as the login for every user. It's a pretty simple request that many websites use these days.
I don't want to monkey patch the core code since this will make upgrading more difficult later on. This means modifying the User model is out of the question. I only want to do a few simple and basic things I expect a few frameworks to do so let me address how Django does it.
Adding new fields to the User model
Django docs says to use create another table and insert the fields there. You will have a one to one relationship between the User table and the Profile table.
eg.
If You want to add an image field to each user you add it to the profile table. A join query is made every single time. They've even specified a constant to tell the framework what table to use:
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
I don't think it's the best practice to have to do a join query every time I want a field that should belong to the user table.
Another option is to use the function add_to_class.
The django community has stated it's not good to define new fields outside of the main class because other developers who add methods won't know all the data members.
Editing old fields
The auth module does a check against two fields username and the hashed password. Looking at the above table I would need to change the username model to accept these properties. Length of 75 with all the valid characters of the email. The django suggests I check against the email field.
Two problems arise if I use the email field to auth against:
I need to write a new class to be used in a constant AUTHENTICATION_BACKEND, so it checks against the email field and I have an unused field called username.
Adding new methods
In MVC/MTV a design principle is to use fat models skinny controllers. Since the model is declared in auth, I'm not sure how one is supposed to add methods that act on the user model's fields. Since django suggests using a Profile model, I suppose they will have to go there.
Extending the User class
A small annoyance would be that I can't use the name 'User' and instead must use 'Users' or 'Accounts'. A bigger one is I don't think the auth would recognize this new module. Meaning I would have to rewrite a bunch functionality that is is present. This one doesn't bother me as it's something I expect to do in other frameworks.
Any comments are appreciated. I wouldn't ask all these questions and look for solutions if I wasn't truly interested in using django.
I agree that django's incessant clinginess to the auth models is absurd. My job requires me to create ultra scalable and very high load sites which sometimes require user authentication and djano's auth model + permissions does not fit with that.
Fortunately, it's not difficult to replace.
First, create a custom User model.
class User(models.Model):
...fields...
#Define some interface methods to be compatible.
def get_and_delete_messages(self):
def is_active(self):
def is_anonymous(self):
def is_authenticated(self):
def is_staff(self):
def has_perm(self, perm_list):
Second, create your own authentication back-end.
class LocalAccount(object):
"""
This checks our local user DB for authentication
"""
def authenticate(self, username=None, password=None):
try:
user = User.objects.get(alias=username)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.select_related().get(pk=user_id)
except User.DoesNotExist:
return None
#settings.py
AUTHENTICATION_BACKENDS = (
'helpers.auth.LocalAccount',
)
That should solve most of your issues, I don't even think all of the methods you would find on django.contrib.auth.User are necessary, I recommend trying it out.
The one gotcha here is that the admin may start to bitch, fortunately that's really easy to patch using simple python inheritance as well. That's another question though :)
At the end of the day your project's auth backend needs some sort of store for auth credentials. That the default auth backend is tightly coupled to the User model is not strange in this respect. It's easy enough to substitute your own definition for the user model if you write your own auth backend, as I have in the past.
I created my Profile model and use AUTH_PROFILE_MODULE, so I have complete control over my model, I can modify fields, add methods, etc. Now I'm thinking about using cache and writing middleware that will get profile from cache if possible.
To login using email you could write very simple auth backend:
from django.contrib.auth.models import User
from django.contrib.auth.backends import ModelBackend
class EmailModelBackend(ModelBackend):
def authenticate(self, username=None, password=None):
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
except User.DoesNotExist:
return None

Django: how to store subdomain-based authentication usernames?

I need to create a subdomain based authentication system, like the one 37signals, freshbooks, codebase use. That is, each subdomain of my main application needs to have its own username namespace. I would like to keep as much as possible of the django authentication system.
What is a good way to store the username?
In particular, it should be possible for different users to have the same username as long as their account belongs to a different subdomain.
Some approaches I've considered, for which I can foresee shortcomings:
storing some prefix in the username field of the django auth user model.
extending the user model according to this.
customizing the source of auth to my needs
I have built this functionality for several sites in the past and have found that your first bullet point is the way to go.
It means that you don't have to make massive change to django auth. What I did was set up a custom authentication backend that abstracts away the way usernames are stored.
auth_backends.py
from django.contrib.auth.backends import ModelBackend
from Home.models import Account
class CustomUserModelBackend(ModelBackend):
def authenticate(self, subdomain, email, password):
try:
user = Account.objects.get(username=u'%s.%s' % (subdomain, email))
if user.check_password(password):
return user
except Account.DoesNotExist:
return None
def get_user(self, user_id):
try:
return Account.objects.get(pk=user_id)
except Account.DoesNotExist:
return None
For this particular project Account was the user model and it just inherited directly from User however you could replace Account with whatever you want.
You have to install the custom auth backend in your settings file:
AUTHENTICATION_BACKENDS = (
'auth_backends.CustomUserModelBackend',
'django.contrib.auth.backends.ModelBackend',
)
Then when you call authenticate you need to pass in the subdomain, email and password.
You can also add some other helper functions or model methods that help with making sure that only the user's actual username is displayed, but that is all pretty trivial.
I think this may be a good use case for using django.contrib.sites in combination with the second bullet item you mentioned. You could create a CustomUser model like so:
from django.contrib.sites.models import Site
class CustomUser(User):
"""User with app settings."""
sites = models.ManyToManyField(Site)
Then you could write a custom auth backend to check that the user can sign in to the current subdomain using the supplied credentials. This allows you to have one username for multiple sites (subdomains) without having to hack the internal auth app or store multiple usernames with custom prefixes.
EDIT: you can get the current site by using Site.objects.get_current() and then check to see if the current site is in the user's sites.
You can read more about the sites framework here: http://docs.djangoproject.com/en/dev/ref/contrib/sites/