Continuing with my posts on Django gotchas, this article is devoted to writing your custom authentication module. There are plenty articles out there (like this and this) dealing with this topic and even the wonderful Django documentation explains how to do it very well. However I wasn’t able to find an article that dealt with my problem specifically although most were close enough to get me going.
The problem I faced can be described in very simple terms:
- I was working with a legacy DB that had its own users table, i.e. I need to make use of the legacy user model
- The DB admin didn’t want to add tables to the DB unless it was absolutely necessary
- I wanted to reuse as much Django code as possible, i.e. use the auth decorators if possible as well as contrib.sessions and the corresponding middleware
Credit goes to the articles mentioned above as they all served as inspiration as well as a friendly chap, who goes by the name claudiu in #django, who not only tried a few things himself but also engaged in discussion several times.
So, much like I was, you are working with a legacy DB and you want to make use of the user model that’s there. Both for authentication and permission checking. Moreover, you want to be able to make use of the auth decorators (@login_required, @permission_required, etc.). And even more: you want your own user model to be in request.user!!! My, my, aren’t you being greedy just now?
You could try extending the User model that comes as part of Django using inheritance as it is described in Scott’s article however that wouldn’t work as you’d have to strip your original User model. The other option could be to create a new Django user every time a new user (from your own user model) logs in but that means duplicating data and you certainly don’t want that. So, what do you do? Well here’s what I did.
Let’s work with a very simple User model (a full User model should come from your inspectdb though):
class MyUser(models.Model): username = models.CharField(unique=True) password = models.CharField() fullname = models.CharField() def is_authenticated(self): return True def get_and_delete_messages(self): return [] def has_perm(self, perm): # Do your permission checking here return True def check_password(self, password): # Do your password checking here return True
This is a very simple user model indeed! Now there’s three methods there that are worth our attention. is_authenticated() always returns True. Why you ask? Well, in our authentication module we will look up Users and return them if the supplied password checks. If we actually return a User object that means that the actual user (the person) has authenticated him/herself correctly. Therefore is_authenticated() returns True for every object instance. This should make @login_required work. Or not? Well, you also need to provide get_and_delete_messages() as for some reason it is called when you log in. I haven’t investigated where/when so if you know please let me know. In my case I always return [] as users don’t get to send/receive messages.
To use @permission_required you will have to implement has_perm(). In it you can go crazy and do the permission checking.
Moving on to a simple custom authentication backend:
from project.app.models import MyUser as User class SimpleAuthBackend: def authenticate(self, username = None, password = None): try: user = User.objects.get(username = username) if user.check_password(password): return user else: return None except User.DoesNotExist: return None def get_user(self, user_id): try: return User.objects.get(id = user_id) except User.DoesNotExist: return None
It couldn’t possibly be any simpler. In authenticate() we look the user up, check if the password supplied is good and return the User object (return None if the authentication failed for some reason). Same procedure goes for get_user() where we return the User object if it exists.
As you can see both authenticate() and get_user() both return a MyUser object. No contrib.auth.models.User objects involved anywhere. This means that whenever you access request.user in your views or user in your templates you’ll actually be accessing a MyUser object. Exactly what we wanted.
DISCLAIMER: I suspect this might break the admin and some other contrib apps yet I still have to check this. If you decide to use any of the code displayed here you’re on your own and even very bad things can happen if you do. Don’t come complaining ![]()
Post a Comment