Django’s authentication framework (
django.contrib.auth) is both pluggable and stackable, which makes integrating custom authentication requirements into Django pretty smooth and easy in most cases. But I had an edge case: the user ids of the backend system could not be guaranteed to conform to Django’s restriction on user names (even with the recent expansion to accommodate email addresses). Thus the usual pattern of creating a Django user corresponding to each backend account required a workaround. At first I tried a hack in which user id from the backend were base64-encoded and munged to conform to Django’s user name limits. Communication with the backend then required de-munging and decoding the Django user name, etc. While this seemed to work it was ugly as hell, and in any case, I was using neither the Django admin site nor Django permissions, so I didn’t need a real Django User model instance for that. On the other hand, I did want to keep the goodness of my pluggable authentication backend and the login view from
django.contrib.auth.views, both of which expect User-like objects.
So, I decided to try something kinda crazy: subclassing
django.contrib.auth.models.AnonymousUser and overriding the “anonymity”.
AnonymousUser really takes advantage of Python duck-typing by mimicking the
django.contrib.auth.models.User without being a Django model itself, and so isn’t tied to Django’s database. Here’s what I came up with:
from django.contrib.auth.models import AnonymousUser class DetachedUser(AnonymousUser): """ Implements a user-like object for user authentication when linkage of a backend user identity to a Django user is undesirable or unnecessary. """ # mark as not hashable -- see also __eq__() and __ne__(), below __hash__ = None # is_active might matter in some contexts, so override is_active = True # # AnonymousUser sets username to '' and id to None, so we need to at least # override those values. # def __init__(self, username): self.username = username self.id = username def __unicode__(self): return self.username # # is_anonymous() and is_authenticated() are key to distinguishing truly # anonymous/unauthenticated users from known/authenticated ones. # def is_anonymous(self): return False def is_authenticated(self): return True # # __eq__ and __ne__ are related to hashing, so be consistent with __hash__, above. # def __eq__(self, other): return NotImplemented def __ne__(self, other): return NotImplemented # # Some django.contrib.auth code may call this method, # e.g, to update the last login time # def save(self): pass
Now I can code the
authenticate methods of my custom authentication backend to return
DetachedUser objects instead of Django users. So far, so good.