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):
        try:
            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):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

Very nice.

tagged: » 10 reactions

Comments

[gravatar]
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

[gravatar]
None 6:44 PM on 14 Aug 2010

Why is django_authenticate required?

[gravatar]
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.

[gravatar]
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"
    func()

A().func()
This prints:
method
global

[gravatar]
Ned Batchelder 7:50 PM on 14 Aug 2010

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

[gravatar]
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.

[gravatar]
Stavros 10:03 PM on 14 Aug 2010

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

[gravatar]
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.

[gravatar]
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: http://copiousfreetime.blogspot.com/2006/12/django-su.html

[gravatar]
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:

name
email
Ignore this:
not displayed and no spam.
Leave this empty:
www
not searched.
 
Name and either email or www are required.
Don't put anything here:
Leave this empty:
URLs auto-link and some tags are allowed: <a><b><i><p><br><pre>.