Source

django-twostepauth / twostepauth / auth_backend.py

#coding: utf-8
from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User, check_password
from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import ugettext_lazy as _
from .utils import RememberComputerTokenGenerator
from . import settings as twostepauth_settings


class TwoStepAuthBackend(ModelBackend):
    """ 
    Authenticates users in two-steps:
         1. username and password validation
         2. one-time password validation
     """
    token_generator = default_token_generator

    def authenticate(self, username=None, password=None, token=None, code=None,
                     remember_token=None, method='APP', force_single_step=False):
        """
        Authenticates a user with a regular password and optionally One-Time code.

        There are three modes available, depending on what parameters the caller supplies:

        1. username and password

        Returns:
            User - if the user identified by this username and password has 2-step inactive
            None - if the user identified by this username and password has 2-step active or
                   username + password is not a valida combination


        2. username, password and code (otp or backup)

        Returns:
            User - if the user identified by this username and password has 2-step active and the code is valid
                   or if the user has 2-step off
            None - if the user identified by this username and password has 2-step active and
                   username + password is not a valid combination or the code is invalid


        3. username, token and code (otp or backup)

        token is a token generated by self.token_generator and identifies a user that has
        passed the first authentication step, i.e., validated a username+password

        Returns:
            User - if the user identified by this username and token has 2-step active and the code is valid
            None - if there is no user identified by this _whatever_, if the code is invalid or if the user
                   doesn't have two-step authentication enabled.


        4. username, password and remember_token

        remember_token is a token saved in the user cookie that allows skiping the second step of the
        authentication for a given number of days

        Returns:
            User - if the user is identified by this username/password and the remember_token value is
                   valid for this user
            None - if the user/pass is wrong or if the remember_token is not valid

        When the optional force_single_step is True, then username and password is enough to authenticate
        a user. This parameter is needed to be able to separate the logins from admin or users and two
        have different settings for these two scenarios.

        TODO some sanity check of the possible combination of parameters?
        """
        if remember_token:
            skip_second_step = self._check_skip_second_step(username, remember_token)
        else:
            skip_second_step = False

        if username and password:
            user = super(TwoStepAuthBackend, self).authenticate(username, password)
            if user is None or force_single_step:
                return user
            try:
                profile = user.get_profile()
            except ObjectDoesNotExist:
                # 2step not enabled - return user
                return user
            if profile.tsa_active and not skip_second_step and not profile.validate(code, method):
                return None
            return user
        elif username and token:
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                return None
            if not self.token_generator.check_token(user, token):
                return None
            try:
                profile = user.get_profile()
            except ObjectDoesNotExist:
                return None
            if profile.tsa_active and profile.validate(code, method):
                return user
            return None
        return None

    def first_step(self, username=None, password=None):
        """
        Validates the username and password and returns a token that can be passed to authenticate with a
        otp to complete the authentication process.
        """
        user = super(TwoStepAuthBackend, self).authenticate(username, password)
        if user is None:
            return None
        return self.token_generator.make_token(user)

    def _check_skip_second_step(self, username, remember_token):
        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            return False
        token_generator = RememberComputerTokenGenerator()
        return token_generator.check_token(user, remember_token)