Search code examples
djangodjango-rest-frameworkdjango-testing

Django Rest Framework Testing


I have a LoginSerializer that has the block of code as below

def validate(self, attrs):
    username = attrs.get('username', '')
    password = attrs.get('password', '')
    user = auth.authenticate(username=username, password=password)
    if user:
        if user.is_active is False:
            raise AuthenticationFailed(
                'Account is disabled, contact admin')
        if not user.is_verified:
            raise AuthenticationFailed('Email is not verified')
        return {
            'username': user.username,
            'firstname': user.firstname,
            'lastname': user.lastname,
            'role': user.role,
            'tokens': user.tokens
        }
    else:
        raise AuthenticationFailed('Invalid credentials, try again')

and a test case as below;

class UserLoginTest(BaseTest):
    def test_inactive_user_can_login(self):
        self.client.post(
            self.register_public, data=valid_user, format='json')
        user = User.objects.get(username=valid_user['username'])
        user.is_verified = True
        user.is_active = False
        user.save()
        response = self.client.post(
            self.login_url, valid_login_user, format='json')
        print(response.data)
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

When I run the test with is_active = False I get Invalid credentials, try again. Why is it that when is_active=False the user is not found even though the user is there? Same with when I try to login from swagger.

EDIT

I have read that I can use

AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.AllowAllUsersModelBackend'] 

then I will be able to check for is_active manually otherwise django handles that and returns a None. What are the dangers of doing this?


Solution

  • This happens because you are using .authenticate() which by default goes through all backends listed in AUTHENTICATION_BACKENDS. If not listed in settings, django.contrib.auth.backends.ModelBackend is used, this backend verifies .is_active() field:

    ...
    def user_can_authenticate(self, user):
        """
        Reject users with is_active=False. Custom user models that don't have
        that attribute are allowed.
        """
        is_active = getattr(user, "is_active", None)
        return is_active or is_active is None
    ...
    

    That snippet runs before your verification and returns None to user variable. So, it fails on if user condition (user is None) thus raise AuthenticationFailed('Invalid credentials, try again')

    About AllowAllUsersModelBackend the only thing it does is override this method to allow inactive users to login:

    class AllowAllUsersModelBackend(ModelBackend):
        def user_can_authenticate(self, user):
            return True
    

    The only risk i can see, is using this backend and not checking .is_active() field manually. Unless if it is intended that inactive users can login into your system.