Django superuser login trapdoor

Friday 13 August 2010

I added an admin trapdoor login to a project the other day. This is the technique where a superuser can log in to a site as any other user. My preferred way to do this is to use the standard login form in a clever way: enter the desired user’s name as the username, and both your superuser name and superuser password into the password field.

But this project was modern enough that I could use a Django authentication backend to get the job done:

from django.contrib.auth import login, authenticate
from django.contrib.auth.models import User

# So I can invoked authenticate recursively below
django_authenticate = authenticate

class SuperuserLoginAuthenticationBackend(object):
    """ Let superusers login as regular users. """
    def authenticate(self, username=None, password=None):
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            return None
        # The password should be name/password
        if "@" not in password:
            return None
        supername, superpass = password.split("@", 1)
        superuser = django_authenticate(username=supername, password=superpass)
        if superuser and superuser.is_superuser:
            return user

    def get_user(self, user_id):
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

Very nice.


Sean O'Donnell 11:31 AM on 14 Aug 2010

Very nice, I've been meaning to cook up something like this for my site, but the implementation here is far nicer than the direction I was thinking of going in, thank you very much

None 6:44 PM on 14 Aug 2010

Why is django_authenticate required?

Ned Batchelder 7:25 PM on 14 Aug 2010

@None: because I wanted to call the Django standard authenticate() function, but I'm inside a method called "authenticate" which is shadowing what I want. By assigning the function I want to a new name, I can invoke it without fear of calling myself by mistake.

None 7:41 PM on 14 Aug 2010

Wouldn't you have to call self.authenticate for that to happen? I'm not seeing this shadowing behavior.

def func():
  print "global"

class A(object):
  def func(self):
    print "method"

This prints:

Ned Batchelder 7:50 PM on 14 Aug 2010

Well what do you know? There's always room to learn more...

Stavros 10:02 PM on 14 Aug 2010

Hmm, where's the code to authenticate the user normally? I only see the superuser authentication code here.

Stavros 10:03 PM on 14 Aug 2010

Also, won't this fail if a user's password contains a @?

Ned Batchelder 10:39 PM on 14 Aug 2010

@Stavros: this is just one authentication backend, you should also continue to use the existing backends, the ones that currently authenticate your users. This backend should be added to the end of the list of authentication backends. I should have mentioned that in the post. If the user has an @ in their password, they'll still authenticate normally with the earlier backend, only falling through to this one if the other backends can't log them in.

Leo Shklovskii 1:15 PM on 15 Aug 2010

There's another option that doesn't require any extra UI, just doing a /su/username URL:

Ned Batchelder 1:32 PM on 15 Aug 2010

@Leo: your solution is also nice. It will require more steps (log in as staff, then visit su url), and in my experience, it will mean I have to remind people what the su url is, but it's also UI-free, I like it.

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
URLs auto-link and some tags are allowed: <a><b><i><p><br><pre>.