From 754010859b43ed9a99547b8e4ae1bd1c5e8d4125 Mon Sep 17 00:00:00 2001 From: Max Hausch Date: Thu, 20 Feb 2025 12:41:43 +0100 Subject: [PATCH] Fix the response status code when authenticating with wrong credentials This conforms to RFC9110, see https://www.rfc-editor.org/rfc/rfc9110#section-15.5.2-2 --- rest_framework/authtoken/serializers.py | 4 ++-- rest_framework/views.py | 2 -- tests/authentication/test_authentication.py | 6 +++--- tests/browsable_api/test_browsable_api.py | 5 +++-- tests/test_decorators.py | 2 +- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py index 63e64d668..635a65927 100644 --- a/rest_framework/authtoken/serializers.py +++ b/rest_framework/authtoken/serializers.py @@ -2,6 +2,7 @@ from django.contrib.auth import authenticate from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +from rest_framework.exceptions import AuthenticationFailed class AuthTokenSerializer(serializers.Serializer): @@ -32,8 +33,7 @@ class AuthTokenSerializer(serializers.Serializer): # users. (Assuming the default ModelBackend authentication # backend.) if not user: - msg = _('Unable to log in with provided credentials.') - raise serializers.ValidationError(msg, code='authorization') + raise AuthenticationFailed() else: msg = _('Must include "username" and "password".') raise serializers.ValidationError(msg, code='authorization') diff --git a/rest_framework/views.py b/rest_framework/views.py index 327ebe903..7a3379586 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -463,8 +463,6 @@ class APIView(View): if auth_header: exc.auth_header = auth_header - else: - exc.status_code = status.HTTP_403_FORBIDDEN exception_handler = self.get_exception_handler() diff --git a/tests/authentication/test_authentication.py b/tests/authentication/test_authentication.py index 2f05ce7d1..b5cbba1e4 100644 --- a/tests/authentication/test_authentication.py +++ b/tests/authentication/test_authentication.py @@ -281,7 +281,7 @@ class SessionAuthTests(TestCase): Ensure POSTing form over session authentication without logged in user fails. """ response = self.csrf_client.post('/session/', {'example': 'example'}) - assert response.status_code == status.HTTP_403_FORBIDDEN + assert response.status_code == status.HTTP_401_UNAUTHORIZED class BaseTokenAuthTests: @@ -440,7 +440,7 @@ class TokenAuthTests(BaseTokenAuthTests, TestCase): {'username': self.username, 'password': "badpass"}, format='json' ) - assert response.status_code == 400 + assert response.status_code == status.HTTP_401_UNAUTHORIZED def test_token_login_json_missing_fields(self): """Ensure token login view using JSON POST fails if missing fields.""" @@ -490,7 +490,7 @@ class IncorrectCredentialsTests(TestCase): permission_classes=() ) response = view(request) - assert response.status_code == status.HTTP_403_FORBIDDEN + assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.data == {'detail': 'Bad credentials'} diff --git a/tests/browsable_api/test_browsable_api.py b/tests/browsable_api/test_browsable_api.py index 758e3d1a4..d386fd4cb 100644 --- a/tests/browsable_api/test_browsable_api.py +++ b/tests/browsable_api/test_browsable_api.py @@ -1,6 +1,7 @@ from django.contrib.auth.models import User from django.test import TestCase, override_settings +from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.test import APIClient @@ -21,14 +22,14 @@ class AnonymousUserTests(TestCase): with self.assertRaises(TypeError): self.client.get('/basicviewset') - def test_get_returns_http_forbidden_when_anonymous_user(self): + def test_get_returns_http_unauthorized_when_anonymous_user(self): old_permissions = BasicModelWithUsersViewSet.permission_classes BasicModelWithUsersViewSet.permission_classes = [IsAuthenticated, OrganizationPermissions] response = self.client.get('/basicviewset') BasicModelWithUsersViewSet.permission_classes = old_permissions - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) @override_settings(ROOT_URLCONF='tests.browsable_api.auth_urls') diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 8d0805cbb..fef63e36e 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -132,7 +132,7 @@ class DecoratorTestCase(TestCase): request = self.factory.get('/') response = view(request) - assert response.status_code == status.HTTP_403_FORBIDDEN + assert response.status_code == status.HTTP_401_UNAUTHORIZED def test_throttle_classes(self): class OncePerDayUserThrottle(UserRateThrottle):