Запрос в тестовом APIClient не проходит после двойной аутентификации
После обновления до django 5 наш тестовый набор начал демонстрировать странное поведение. Мы используем DRF's APIClient и аутентификация больше не работает так, как ожидалось. Некоторые API-запросы возвращают rest_framework.exceptions.NotAuthenticated
со статусом 403 (кстати, если кто-то может объяснить, почему этот статус 403, а не 401, я был бы признателен).
Это кажется произвольным. Из двух запросов к представлениям, которые реализованы одинаково с точки зрения пользовательских прав и т.д., один возвращает 403 только во время тестирования. Выполнение тех же вызовов в Postman не воспроизводит ошибку. Таким образом, некоторые из точно таких же тестовых примеров, которые раньше проходили на django 4, теперь не проходят на django 5 из-за вышеуказанной ошибки. Мне не удалось воспроизвести эти случаи в минимальном примере.
Однако я воспроизвел следующее:
client.force_authenticate(user)
client.get(url) # the user does not have permissions. response is 403, as expected
client.force_authenticate(None) # "log out"
user.user_permissions.add(permission)
client.force_authenticate(user)
response = client.get(url) # the now has permissions. response 200 is expected
assert response.status_code == 200 # response status is 403
Версии пакетов следующие
django==5.0.3
djangorestframework==3.15.0
pytest==7.4.3
pytest-django==4.7.0
Я сделал минимальный проект, в котором это можно наблюдать.
Насколько мне удалось проверить, такое неожиданное поведение встречается как в django 4, так и в 5. Может быть, я неправильно использую тестовый клиент?
Это поведение описано в документации:
ModelBackend кэширует разрешения на объект пользователя после первого когда они должны быть получены для проверки разрешений. Это обычно хорошо подходит для цикла запрос-ответ, поскольку разрешения не обычно проверяются сразу после их добавления (например, в админке). например). Если вы добавляете разрешения и проверяете их сразу после этого, например, в тесте или представлении, то самым простым решением будет повторно получить пользователя из базы данных.
Решение:
client.force_authenticate(user=user)
client.get("/test/")
client.force_authenticate(user=None)
user.user_permissions.add(permission)
# Getting a new user instance.
# user.refresh_from_db() does not clear cache
user = get_object_or_404(User, pk=user.id)
client.force_authenticate(user=user)
response = client.get("/test/")
assert response.status_code == 200
Что касается response.status_code
, возвращаемого для неаутентифицированного запроса, то в документации DRF есть следующая информация:
Когда неаутентифицированному запросу отказано в разрешении, существует два различных кодов ошибок, которые могут быть уместны.
HTTP 401 Unauthorized
HTTP 403 Permission Denied
HTTP 401 всегда должен содержать заголовок WWW-Authenticate, который указывает клиенту, как пройти аутентификацию. клиенту, как пройти аутентификацию. Ответы HTTP 403 не содержат WWW-Authenticate заголовок.
Тип ответа, который будет использоваться, зависит от схемы аутентификации схемы. Хотя может использоваться несколько схем аутентификации, только одна схема может быть использована для определения типа ответа. Первый класс аутентификации, установленный в представлении, используется при определении типа ответа.
Если в настройках не указаны DEFAULT_AUTHENTICATION_CLASSES, то используются классы по умолчанию:
[
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
]
После отключения класса SessionAuthentication (или изменения порядка классов аутентификации, сначала BasicAuthentication) для неаутентифицированных запросов возвращаются ответы 401.