diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py index 880563bc5b..6be3552cad 100644 --- a/django/contrib/auth/middleware.py +++ b/django/contrib/auth/middleware.py @@ -1,7 +1,8 @@ +import warnings from functools import partial from urllib.parse import urlsplit -from asgiref.sync import iscoroutinefunction, markcoroutinefunction +from asgiref.sync import iscoroutinefunction, markcoroutinefunction, sync_to_async from django.conf import settings from django.contrib import auth @@ -10,7 +11,7 @@ from django.contrib.auth.backends import RemoteUserBackend from django.contrib.auth.views import redirect_to_login from django.core.exceptions import ImproperlyConfigured from django.shortcuts import resolve_url -from django.utils.deprecation import MiddlewareMixin +from django.utils.deprecation import MiddlewareMixin, RemovedInDjango61Warning from django.utils.functional import SimpleLazyObject @@ -128,6 +129,10 @@ class RemoteUserMiddleware: def __call__(self, request): if self.is_async: return self.__acall__(request) + self.process_request(request) + return self.get_response(request) + + def process_request(self, request): # AuthenticationMiddleware is required so that request.user exists. if not hasattr(request, "user"): raise ImproperlyConfigured( @@ -145,13 +150,13 @@ class RemoteUserMiddleware: # AnonymousUser by the AuthenticationMiddleware). if self.force_logout_if_no_header and request.user.is_authenticated: self._remove_invalid_user(request) - return self.get_response(request) + return # If the user is already authenticated and that user is the user we are # getting passed in the headers, then the correct user is already # persisted in the session and we don't need to continue. if request.user.is_authenticated: if request.user.get_username() == self.clean_username(username, request): - return self.get_response(request) + return else: # An authenticated user is associated with the request, but # it does not match the authorized user in the header. @@ -165,9 +170,26 @@ class RemoteUserMiddleware: # by logging the user in. request.user = user auth.login(request, user) - return self.get_response(request) async def __acall__(self, request): + # RemovedInDjango61Warning. + if ( + self.__class__.process_request is not RemoteUserMiddleware.process_request + and self.__class__.aprocess_request is RemoteUserMiddleware.aprocess_request + ): + warnings.warn( + "Support for subclasses of RemoteUserMiddleware that override " + "process_request() without overriding aprocess_request() is " + "deprecated.", + category=RemovedInDjango61Warning, + stacklevel=2, + ) + await sync_to_async(self.process_request, thread_sensitive=True)(request) + return await self.get_response(request) + await self.aprocess_request(request) + return await self.get_response(request) + + async def aprocess_request(self, request): # AuthenticationMiddleware is required so that request.user exists. if not hasattr(request, "user"): raise ImproperlyConfigured( @@ -187,14 +209,14 @@ class RemoteUserMiddleware: user = await request.auser() if user.is_authenticated: await self._aremove_invalid_user(request) - return await self.get_response(request) + return user = await request.auser() # If the user is already authenticated and that user is the user we are # getting passed in the headers, then the correct user is already # persisted in the session and we don't need to continue. if user.is_authenticated: if user.get_username() == self.clean_username(username, request): - return await self.get_response(request) + return else: # An authenticated user is associated with the request, but # it does not match the authorized user in the header. @@ -209,8 +231,6 @@ class RemoteUserMiddleware: request.user = user await auth.alogin(request, user) - return await self.get_response(request) - def clean_username(self, username, request): """ Allow the backend to clean the username, if the backend defines a diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 2e0dbb02e5..e6db513dae 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -55,6 +55,10 @@ details on these changes. ``django.contrib.postgres.aggregates.JSONBAgg``, and ``django.contrib.postgres.aggregates.StringAgg`` will be removed. +* Support for subclasses of ``RemoteUserMiddleware`` that override + ``process_request()`` without overriding ``aprocess_request()`` will be + removed. + .. _deprecation-removed-in-6.0: 6.0 diff --git a/docs/releases/5.2.2.txt b/docs/releases/5.2.2.txt index f6787bff08..2cf5c750ff 100644 --- a/docs/releases/5.2.2.txt +++ b/docs/releases/5.2.2.txt @@ -18,3 +18,7 @@ Bugfixes * Fixed a regression in Django 5.2 that caused a crash when no arguments were passed into ``QuerySet.union()`` (:ticket:`36388`). + +* Fixed a regression in Django 5.2 where subclasses of ``RemoteUserMiddleware`` + that had overridden ``process_request()`` were no longer supported + (:ticket:`36390`). diff --git a/docs/releases/5.2.txt b/docs/releases/5.2.txt index 32a0b35951..feaeb9436a 100644 --- a/docs/releases/5.2.txt +++ b/docs/releases/5.2.txt @@ -488,3 +488,7 @@ Miscellaneous ``django.contrib.postgres.aggregates.JSONBAgg``, and ``django.contrib.postgres.aggregates.StringAgg`` is deprecated in favor of the ``order_by`` argument. + +* Support for subclasses of ``RemoteUserMiddleware`` that override + ``process_request()`` without overriding ``aprocess_request()`` is + deprecated. diff --git a/tests/auth_tests/test_remote_user.py b/tests/auth_tests/test_remote_user.py index 67748d6c23..4b5902b586 100644 --- a/tests/auth_tests/test_remote_user.py +++ b/tests/auth_tests/test_remote_user.py @@ -13,6 +13,7 @@ from django.test import ( modify_settings, override_settings, ) +from django.utils.deprecation import RemovedInDjango61Warning @override_settings(ROOT_URLCONF="auth_tests.urls") @@ -487,3 +488,51 @@ class PersistentRemoteUserTest(RemoteUserTest): response = await self.async_client.get("/remote_user/") self.assertFalse(response.context["user"].is_anonymous) self.assertEqual(response.context["user"].username, "knownuser") + + +# RemovedInDjango61Warning. +class CustomProcessRequestMiddlewareSyncOnly(RemoteUserMiddleware): + def process_request(self, request): + raise NotImplementedError("process_request has not been implemented.") + + +# RemovedInDjango61Warning. +class CustomProcessRequestMiddleware(RemoteUserMiddleware): + def process_request(self, request): + raise NotImplementedError("process_request has not been implemented.") + + async def aprocess_request(self, request): + raise NotImplementedError("aprocess_request has not been implemented.") + + +# RemovedInDjango61Warning. +@override_settings(ROOT_URLCONF="auth_tests.urls") +class CustomProcessRequestMiddlewareTest(TestCase): + @modify_settings( + MIDDLEWARE={ + "append": "auth_tests.test_remote_user." + "CustomProcessRequestMiddlewareSyncOnly" + } + ) + async def test_async_warns_sync_only_middleware(self): + deprecation_msg = ( + "Support for subclasses of RemoteUserMiddleware that override " + "process_request() without overriding aprocess_request() is " + "deprecated." + ) + error_msg = "process_request has not been implemented." + with ( + self.assertWarnsMessage(RemovedInDjango61Warning, deprecation_msg), + self.assertRaisesMessage(NotImplementedError, error_msg), + ): + await self.async_client.get("/remote_user/") + + @modify_settings( + MIDDLEWARE={ + "append": "auth_tests.test_remote_user.CustomProcessRequestMiddleware" + } + ) + async def test_async_no_warning_sync_and_async_middleware(self): + error_msg = "aprocess_request has not been implemented." + with self.assertRaisesMessage(NotImplementedError, error_msg): + await self.async_client.get("/remote_user/")