瀏覽代碼

Fixed #25029 -- Added PersistentRemoteUserMiddleware for login-page-only external authentication.

Jan Pazdziora 9 年之前
父節點
當前提交
a570701e02

+ 1 - 0
AUTHORS

@@ -308,6 +308,7 @@ answer newbie questions, and generally made Django that much better:
     James Wheare <django@sparemint.com>
     James Wheare <django@sparemint.com>
     Jannis Leidel <jannis@leidel.info>
     Jannis Leidel <jannis@leidel.info>
     Janos Guljas
     Janos Guljas
+    Jan Pazdziora
     Jan Rademaker
     Jan Rademaker
     Jarek Zgoda <jarek.zgoda@gmail.com>
     Jarek Zgoda <jarek.zgoda@gmail.com>
     Jason Davies (Esaj) <http://www.jasondavies.com/>
     Jason Davies (Esaj) <http://www.jasondavies.com/>

+ 15 - 1
django/contrib/auth/middleware.py

@@ -53,6 +53,7 @@ class RemoteUserMiddleware(object):
     # used in the request.META dictionary, i.e. the normalization of headers to
     # used in the request.META dictionary, i.e. the normalization of headers to
     # all uppercase and the addition of "HTTP_" prefix apply.
     # all uppercase and the addition of "HTTP_" prefix apply.
     header = "REMOTE_USER"
     header = "REMOTE_USER"
+    force_logout_if_no_header = True
 
 
     def process_request(self, request):
     def process_request(self, request):
         # AuthenticationMiddleware is required so that request.user exists.
         # AuthenticationMiddleware is required so that request.user exists.
@@ -69,7 +70,7 @@ class RemoteUserMiddleware(object):
             # If specified header doesn't exist then remove any existing
             # If specified header doesn't exist then remove any existing
             # authenticated remote-user, or return (leaving request.user set to
             # authenticated remote-user, or return (leaving request.user set to
             # AnonymousUser by the AuthenticationMiddleware).
             # AnonymousUser by the AuthenticationMiddleware).
-            if request.user.is_authenticated():
+            if self.force_logout_if_no_header and request.user.is_authenticated():
                 self._remove_invalid_user(request)
                 self._remove_invalid_user(request)
             return
             return
         # If the user is already authenticated and that user is the user we are
         # If the user is already authenticated and that user is the user we are
@@ -118,3 +119,16 @@ class RemoteUserMiddleware(object):
         else:
         else:
             if isinstance(stored_backend, RemoteUserBackend):
             if isinstance(stored_backend, RemoteUserBackend):
                 auth.logout(request)
                 auth.logout(request)
+
+
+class PersistentRemoteUserMiddleware(RemoteUserMiddleware):
+    """
+    Middleware for Web-server provided authentication on logon pages.
+
+    Like RemoteUserMiddleware but keeps the user authenticated even if
+    the header (``REMOTE_USER``) is not found in the request. Useful
+    for setups when the external authentication via ``REMOTE_USER``
+    is only expected to happen on some "logon" URL and the rest of
+    the application wants to use Django's authentication mechanism.
+    """
+    force_logout_if_no_header = False

+ 24 - 1
docs/howto/auth-remote-user.txt

@@ -19,7 +19,8 @@ When the Web server takes care of authentication it typically sets the
 ``REMOTE_USER`` environment variable for use in the underlying application.  In
 ``REMOTE_USER`` environment variable for use in the underlying application.  In
 Django, ``REMOTE_USER`` is made available in the :attr:`request.META
 Django, ``REMOTE_USER`` is made available in the :attr:`request.META
 <django.http.HttpRequest.META>` attribute.  Django can be configured to make
 <django.http.HttpRequest.META>` attribute.  Django can be configured to make
-use of the ``REMOTE_USER`` value using the ``RemoteUserMiddleware`` and
+use of the ``REMOTE_USER`` value using the ``RemoteUserMiddleware``
+or ``PersistentRemoteUserMiddleware``, and
 :class:`~django.contrib.auth.backends.RemoteUserBackend` classes found in
 :class:`~django.contrib.auth.backends.RemoteUserBackend` classes found in
 :mod:`django.contrib.auth`.
 :mod:`django.contrib.auth`.
 
 
@@ -95,3 +96,25 @@ If your authentication mechanism uses a custom HTTP header and not
 If you need more control, you can create your own authentication backend
 If you need more control, you can create your own authentication backend
 that inherits from :class:`~django.contrib.auth.backends.RemoteUserBackend` and
 that inherits from :class:`~django.contrib.auth.backends.RemoteUserBackend` and
 override one or more of its attributes and methods.
 override one or more of its attributes and methods.
+
+.. _persistent-remote-user-middleware-howto:
+
+Using ``REMOTE_USER`` on login pages only
+=========================================
+
+.. versionadded:: 1.9
+
+The ``RemoteUserMiddleware`` authentication middleware assumes that the HTTP
+request header ``REMOTE_USER`` is present with all authenticated requests. That
+might be expected and practical when Basic HTTP Auth with ``htpasswd`` or other
+simple mechanisms are used, but with Negotiate (GSSAPI/Kerberos) or other
+resource intensive authentication methods, the authentication in the front-end
+HTTP server is usually only set up for one or a few login URLs, and after
+successful authentication, the application is supposed to maintain the
+authenticated session itself.
+
+:class:`~django.contrib.auth.middleware.PersistentRemoteUserMiddleware`
+provides support for this use case. It will maintain the authenticated session
+until explicit logout by the user. The class can be used as a drop-in
+replacement of :class:`~django.contrib.auth.middleware.RemoteUserMiddleware`
+in the documentation above.

+ 8 - 0
docs/ref/middleware.txt

@@ -374,6 +374,14 @@ every incoming ``HttpRequest`` object. See :ref:`Authentication in Web requests
 Middleware for utilizing Web server provided authentication. See
 Middleware for utilizing Web server provided authentication. See
 :doc:`/howto/auth-remote-user` for usage details.
 :doc:`/howto/auth-remote-user` for usage details.
 
 
+.. class:: PersistentRemoteUserMiddleware
+
+.. versionadded:: 1.9
+
+Middleware for utilizing Web server provided authentication when enabled only
+on the login page. See :ref:`persistent-remote-user-middleware-howto` for usage
+details.
+
 .. class:: SessionAuthenticationMiddleware
 .. class:: SessionAuthenticationMiddleware
 
 
 Allows a user's sessions to be invalidated when their password changes. See
 Allows a user's sessions to be invalidated when their password changes. See

+ 4 - 0
docs/releases/1.9.txt

@@ -172,6 +172,10 @@ Minor features
   :func:`~django.contrib.auth.decorators.permission_required()` accepts all
   :func:`~django.contrib.auth.decorators.permission_required()` accepts all
   kinds of iterables, not only list and tuples.
   kinds of iterables, not only list and tuples.
 
 
+* The new :class:`~django.contrib.auth.middleware.PersistentRemoteUserMiddleware`
+  makes it possible to use ``REMOTE_USER`` for setups where the header is only
+  populated on login pages instead of every request in the session.
+
 :mod:`django.contrib.gis`
 :mod:`django.contrib.gis`
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 

+ 23 - 0
tests/auth_tests/test_remote_user.py

@@ -232,3 +232,26 @@ class CustomHeaderRemoteUserTest(RemoteUserTest):
         'auth_tests.test_remote_user.CustomHeaderMiddleware'
         'auth_tests.test_remote_user.CustomHeaderMiddleware'
     )
     )
     header = 'HTTP_AUTHUSER'
     header = 'HTTP_AUTHUSER'
+
+
+class PersistentRemoteUserTest(RemoteUserTest):
+    """
+    PersistentRemoteUserMiddleware keeps the user logged in even if the
+    subsequent calls do not contain the header value.
+    """
+    middleware = 'django.contrib.auth.middleware.PersistentRemoteUserMiddleware'
+    require_header = False
+
+    def test_header_disappears(self):
+        """
+        A logged in user is kept logged in even if the REMOTE_USER header
+        disappears during the same browser session.
+        """
+        User.objects.create(username='knownuser')
+        # Known user authenticates
+        response = self.client.get('/remote_user/', **{self.header: self.known_user})
+        self.assertEqual(response.context['user'].username, 'knownuser')
+        # Should stay logged in if the REMOTE_USER header disappears.
+        response = self.client.get('/remote_user/')
+        self.assertEqual(response.context['user'].is_anonymous(), False)
+        self.assertEqual(response.context['user'].username, 'knownuser')