diff --git a/djoser/serializers.py b/djoser/serializers.py index ae42e77f..f9016dc3 100644 --- a/djoser/serializers.py +++ b/djoser/serializers.py @@ -117,6 +117,9 @@ def __init__(self, *args, **kwargs): def validate(self, attrs): password = attrs.get("password") + # https://github.com/sunscrapers/djoser/issues/389 + # https://github.com/sunscrapers/djoser/issues/429 + # https://github.com/sunscrapers/djoser/issues/795 params = {"username": attrs.get(settings.LOGIN_FIELD)} self.user = authenticate( request=self.context.get("request"), **params, password=password diff --git a/testproject/testapp/tests/test_token_create.py b/testproject/testapp/tests/test_token_create.py index 028decae..cc01fde5 100644 --- a/testproject/testapp/tests/test_token_create.py +++ b/testproject/testapp/tests/test_token_create.py @@ -1,9 +1,4 @@ -from django.conf import settings as django_settings -from unittest import mock - from django.contrib.auth import user_logged_in, user_login_failed -from django.contrib.auth.backends import ModelBackend -from django.test import override_settings from djet import assertions from rest_framework import status from rest_framework.reverse import reverse @@ -39,216 +34,6 @@ def test_post_should_login_user(self): self.assertNotEqual(user.last_login, previous_last_login) self.assertTrue(self.signal_sent) - @override_settings( - AUTHENTICATION_BACKENDS=[ - "django.contrib.auth.backends.ModelBackend", - ], - DJOSER=dict(django_settings.DJOSER, **{"LOGIN_FIELD": "username"}), - ) - def test_login__LOGIN_FIELD_username__USERNAME_FIELD_username( - self, - ): - user = create_user() - user_logged_in.connect(self.signal_receiver) - previous_last_login = user.last_login - - with mock.patch("djoser.serializers.User.USERNAME_FIELD", "username"): - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=True - ): - response = self.client.post( - self.base_url, - {"username": user.username, "password": user.raw_password}, - ) - self.assert_status_equal(response, status.HTTP_200_OK) - - user.refresh_from_db() - self.assertEqual(response.data["auth_token"], user.auth_token.key) - self.assertNotEqual(user.last_login, previous_last_login) - self.assertTrue(self.signal_sent) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=False - ): - response = self.client.post( - self.base_url, - {"username": user.username, "password": user.raw_password}, - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=True - ): - response = self.client.post( - self.base_url, {"email": user.email, "password": user.raw_password} - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=False - ): - response = self.client.post( - self.base_url, {"email": user.email, "password": user.raw_password} - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - @override_settings( - AUTHENTICATION_BACKENDS=[ - "django.contrib.auth.backends.ModelBackend", - ], - DJOSER=dict(django_settings.DJOSER, **{"LOGIN_FIELD": "email"}), - ) - def test_login__LOGIN_FIELD_email__USERNAME_FIELD_username( - self, - ): - user = create_user() - user_logged_in.connect(self.signal_receiver) - previous_last_login = user.last_login - - with mock.patch("djoser.serializers.User.USERNAME_FIELD", "username"): - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=True - ): - response = self.client.post( - self.base_url, - {"username": user.username, "password": user.raw_password}, - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=False - ): - response = self.client.post( - self.base_url, - {"username": user.username, "password": user.raw_password}, - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=True - ): - response = self.client.post( - self.base_url, {"email": user.email, "password": user.raw_password} - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=False - ): - response = self.client.post( - self.base_url, {"email": user.email, "password": user.raw_password} - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - user.refresh_from_db() - self.assertEqual(user.last_login, previous_last_login) - self.assertFalse(self.signal_sent) - - @override_settings( - AUTHENTICATION_BACKENDS=[ - "django.contrib.auth.backends.ModelBackend", - ], - DJOSER=dict(django_settings.DJOSER, **{"LOGIN_FIELD": "username"}), - ) - def test_login__LOGIN_FIELD_username__USERNAME_FIELD_email( - self, - ): - user = create_user() - user_logged_in.connect(self.signal_receiver) - previous_last_login = user.last_login - - with mock.patch("djoser.serializers.User.USERNAME_FIELD", "email"): - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=True - ): - response = self.client.post( - self.base_url, - {"username": user.username, "password": user.raw_password}, - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=False - ): - response = self.client.post( - self.base_url, - {"username": user.username, "password": user.raw_password}, - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=True - ): - response = self.client.post( - self.base_url, {"email": user.email, "password": user.raw_password} - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=False - ): - response = self.client.post( - self.base_url, {"email": user.email, "password": user.raw_password} - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - user.refresh_from_db() - self.assertEqual(user.last_login, previous_last_login) - self.assertFalse(self.signal_sent) - - @override_settings( - AUTHENTICATION_BACKENDS=[ - "django.contrib.auth.backends.ModelBackend", - ], - DJOSER=dict(django_settings.DJOSER, **{"LOGIN_FIELD": "email"}), - ) - def test_login__LOGIN_FIELD_email__USERNAME_FIELD_email( - self, - ): - user = create_user() - user_logged_in.connect(self.signal_receiver) - previous_last_login = user.last_login - - with mock.patch("djoser.serializers.User.USERNAME_FIELD", "email"): - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=True - ): - response = self.client.post( - self.base_url, - {"username": user.username, "password": user.raw_password}, - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=False - ): - response = self.client.post( - self.base_url, - {"username": user.username, "password": user.raw_password}, - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=True - ): - response = self.client.post( - self.base_url, {"email": user.email, "password": user.raw_password} - ) - self.assert_status_equal(response, status.HTTP_200_OK) - - user.refresh_from_db() - self.assertEqual(response.data["auth_token"], user.auth_token.key) - self.assertNotEqual(user.last_login, previous_last_login) - self.assertTrue(self.signal_sent) - - with mock.patch.object( - ModelBackend, "user_can_authenticate", return_value=False - ): - response = self.client.post( - self.base_url, {"email": user.email, "password": user.raw_password} - ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - def test_post_should_not_login_if_user_is_not_active(self): """In Django >= 1.10 authenticate() returns None if user is inactive, while in Django < 1.10 authenticate() succeeds if user is inactive.""" diff --git a/testproject/testapp/tests/test_token_create_custom_username_login_fields.py b/testproject/testapp/tests/test_token_create_custom_username_login_fields.py new file mode 100644 index 00000000..27769184 --- /dev/null +++ b/testproject/testapp/tests/test_token_create_custom_username_login_fields.py @@ -0,0 +1,136 @@ +from unittest import mock + +import pytest +from django.contrib.auth import user_logged_in, user_login_failed +from django.contrib.auth.backends import ModelBackend +from rest_framework import status +from rest_framework.reverse import reverse + +from testapp.tests.common import create_user + + +@pytest.mark.django_db +class TestUsernameLoginFields: + url = reverse("login") + + @pytest.fixture(autouse=True) + def settings(self, settings): + settings.AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", + ] + return settings + + @pytest.fixture + def user(self): + return create_user() + + @pytest.fixture + def signal_user_logged_in_patched(self): + signal_handler = mock.MagicMock() + user_logged_in.connect(signal_handler) + return signal_handler + + @pytest.fixture + def signal_user_login_failed_patched(self): + signal_handler = mock.MagicMock() + user_login_failed.connect(signal_handler) + return signal_handler + + def configure_djoser_settings( + self, settings, mocker, login_field, username_field, user_can_authenticate + ): + settings.DJOSER["LOGIN_FIELD"] = login_field + mocker.patch("djoser.serializers.settings.LOGIN_FIELD", login_field) + mocker.patch("djoser.serializers.User.USERNAME_FIELD", username_field) + mocker.patch.object( + ModelBackend, "user_can_authenticate", return_value=user_can_authenticate + ) + + @pytest.mark.parametrize( + "login_field, username_field, send_field", + [ + ("username", "username", "username"), + ("email", "email", "email"), + ], + ) + def test_successful_login( + self, + user, + client, + settings, + mocker, + signal_user_logged_in_patched, + login_field, + username_field, + send_field, + ): + self.configure_djoser_settings( + settings=settings, + mocker=mocker, + login_field=login_field, + username_field=username_field, + user_can_authenticate=True, + ) + + if send_field == "username": + data = {"username": user.username, "password": user.raw_password} + else: + data = {"email": user.email, "password": user.raw_password} + + previous_last_login = user.last_login + response = client.post(self.url, data) + + assert response.status_code == status.HTTP_200_OK + user.refresh_from_db() + + assert response.data["auth_token"] == user.auth_token.key + assert user.last_login != previous_last_login + signal_user_logged_in_patched.assert_called_once() + + @pytest.mark.parametrize( + "login_field, username_field, user_can_authenticate, send_field", + [ + ("username", "username", False, "username"), + ("username", "username", True, "email"), + ("username", "email", True, "username"), + ("username", "email", False, "username"), + ("email", "username", False, "username"), + ("email", "username", True, "email"), + ("email", "email", True, "username"), + ("email", "email", False, "username"), + ("username", "email", True, "email"), + ("email", "username", True, "username"), + ], + ) + def test_failing_login( + self, + user, + client, + settings, + mocker, + signal_user_login_failed_patched, + login_field, + username_field, + send_field, + user_can_authenticate, + ): + self.configure_djoser_settings( + settings=settings, + mocker=mocker, + login_field=login_field, + username_field=username_field, + user_can_authenticate=user_can_authenticate, + ) + if send_field == "username": + data = {"username": user.username, "password": user.raw_password} + else: + data = {"email": user.email, "password": user.raw_password} + + previous_last_login = user.last_login + response = client.post(self.url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + user.refresh_from_db() + + assert user.last_login == previous_last_login + signal_user_login_failed_patched.assert_called_once()