Email or username authentication with masquerading

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
from django.forms.fields import email_re

class EmailBackend(ModelBackend):
    """Allows a user to login using their email address, and not just their
    username. This is a lot more common than a new username. Some things that
    it takes care of for you are:

    - Allow _either_ username or email to be used
    - Allow anyone marked as staff in the database to mascquerade as another
      user by using the user they want to masquerade as as the username and
      using <username>/<password> in the password field, where <username>
      is _their_ username."""

    def _lookup_user(self, username):
        try:
            if email_re.search(username):
                # Looks like an email. Since emails are not case sensitive
                # and many users have a habit of typing them in mixed
                # cases, we will normalize them to lower case. This assumes
                # that the database has done the same thing.
                user = User.objects.get(email=username.lower())
            else:
                user = User.objects.get(username=username)
        except User.DoesNotExist:
                return None

        return user
            
    def authenticate(self, username=None, password=None):
        user = self._lookup_user(username)

        if user:
            if user.check_password(password):
                return user
            elif '/' in password:
                proposed_user = user    # Who we want to be
                (username, password) = password.split('/', 1)
                user = self._lookup_user(username)
                if user and user.is_staff:
                    if user.check_password(password):
                        return proposed_user
        return None
            

More like this

  1. Login with email or username by zeeg 5 years, 8 months ago
  2. MoinMoin auth backend by yourcelf 3 years, 6 months ago
  3. Fixture for test users by V 5 years, 7 months ago
  4. LoginAsForm - Login as any User without a password by johnboxall 4 years, 10 months ago
  5. phpbb (2.x) authentication backend by bram 5 years, 8 months ago

Comments

christian.oudard (on June 26, 2009):

Good snippet. One issue, however; line 39 should use

username, password) = password.split('/', 1)

in case there is a / in the password.

Also, the email_re is unnecessary here. If you just check whether the username matches an email, the validation on the email database field will take care of it:

try:
    user = User.objects.get(email=username.lower())
except User.DoesNotExist:
    user = User.objects.get(username=username)

#

petrilli (on June 28, 2009):

Thanks for the changes. I'm not sure about the expense of hitting the database v. the regex, since it's compiled. I'll do some little micro-benchmarks. I'm not sure it matters, in the end since it's only done on log-in. You're right about the password bit, though.

#

akaihola (on November 15, 2011):

Can't you just do a User.objects.get(email__iexact=username) on line 23?

#

akaihola (on November 15, 2011):

Also, nowadays the correct import for email_re is from django.core.validators import email_re.

#

(Forgotten your password?)