Home > other >  Why is Django check_password=True but authenticate=None
Why is Django check_password=True but authenticate=None

Time:01-22

I'm trying to write a unit test for a login endpoint in Django using as much of the built in functionality as possible.

There are existing tests that confirm that the account create endpoint is functioning properly.

In the login view, however, the check_password() function will return True for this test, but the authenticate() function returns None.

Is it safe to use the check_password() function instead?

Otherwise, how do I update this code to use the authenticate() function?

accounts.py

class Account(AbstractUser):
    username = models.CharField(max_length=150, unique=True, null=False, default='')
    password = models.CharField(max_length=100)
    ...

    REQUIRED_FIELDS = ['username', 'password']

    class Meta:
        app_label = 'accounts'
        db_table = 'accounts_account'

    objects = models.Manager()

test_login.py

def test_login(self):
    # Create account
    request_create = self.factory.post('/accounts/account',
                                       self.request_data,
                                       content_type='application/json')
    view = AccountView.as_view()
    response_create = view(request_create)

    # Login account
    request_login = self.factory.post('/accounts/login',
                                      self.request_data,
                                      content_type='application/json')

    view = LoginView.as_view()
    response = view(request_login)

views.py

class LoginView(View):

    def post(self, request):
        r = json.loads(request.body)
        username = r.get('username')
        password = r.get('password')
        cp = check_password(password, Account.objects.get(username=username).password)
        user = authenticate(username=username, password=password)

P.S. I've checked this thread and is_active is set to true.

CodePudding user response:

It is safe. The main difference is that by using check_password() you are manually checking an User from an authentication backend, thus you have to retrieve the user object and compare its password with the plain text like you do at:

check_password(password, Account.objects.get(username=username).password)

While, with authenticate() it is possible to check credentials against several authentication backends. Meaning that with the former you couldn't hook your application with other authentication sources

You are missing some code in tests.py and views.py. That being said, here is a full test of this LoginView:

tests.py

class TestClientLogin(TestCase):

    def setUp(self):
        User = get_user_model()
        self.factory = RequestFactory()
        self.user = User.objects.create_user(
            username='test_user',
            email='[email protected]',
            password='super_secret'
        )

    
    def test_user_login(self):
        request_data = {'username': 'test_user', 'password': 'super_secret'}
        request = self.factory.post(
            '/accounts/login/', 
            request_data , 
            content_type='application/json'
        )

        response = LoginView.as_view()(request)
        data = json.loads(response.content)

        self.assertEqual(self.user.username, data['username'])
        self.assertEqual(response.status_code, 200)

views.py

class LoginView(View):

    def post(self, request):
        data = json.loads(request.body)
        username = data.get('username')
        password = data.get('password')

        user = authenticate(username=username, password=password)

        if user is not None:
            return JsonResponse({'username': user.username})
        else:
             return JsonResponse({'username': None})

CodePudding user response:

If you want use as much of the built in functionality as possible:

settings.py:

AUTH_USER_MODEL = 'accounts.Account'

accounts/models.py:

class Account(AbstractUser):
    # you dont need to define username or password again
    ...

    REQUIRED_FIELDS = ['username', 'password']

    class Meta:
        # you don't need to define default app_label, db_table

     objects = UserManager()  # otherwise don't work "python manage.py createsuperuser"

accounts/views.py:

from django.contrib.auth.views import LoginView


class ClientLogin(LoginView):
    pass

accounts/tests.py:

from django.test import TestCase, Client
from django.utils.crypto import get_random_string

class TestClientLogin(TestCase):

    def setUp(self):
        User = get_user_model()
        self.username, self.password = get_random_string(20), get_random_string(20)  # don't use constants in tests
        self.user = User.objects.create_user(username=self.username, password=self.password )

    def test_user_login(self):
        request_data = {'username': self.username, 'password': self.password}
        response = Client().post('/accounts/login/', request_data)
        self.assertEqual(response.status_code, 302)

BUT:

you don't need to test ClientLogin, you don't have any code in view.

  • Related