Browse Source

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

Jan Pazdziora 9 years ago
parent
commit
a570701e02

+ 1 - 0
AUTHORS

@@ -308,6 +308,7 @@ answer newbie questions, and generally made Django that much better:
     James Wheare <django@sparemint.com>
     Jannis Leidel <jannis@leidel.info>
     Janos Guljas
+    Jan Pazdziora
     Jan Rademaker
     Jarek Zgoda <jarek.zgoda@gmail.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
     # all uppercase and the addition of "HTTP_" prefix apply.
     header = "REMOTE_USER"
+    force_logout_if_no_header = True
 
     def process_request(self, request):
         # 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
             # authenticated remote-user, or return (leaving request.user set to
             # 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)
             return
         # If the user is already authenticated and that user is the user we are
@@ -118,3 +119,16 @@ class RemoteUserMiddleware(object):
         else:
             if isinstance(stored_backend, RemoteUserBackend):
                 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
 Django, ``REMOTE_USER`` is made available in the :attr:`request.META
 <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
 :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
 that inherits from :class:`~django.contrib.auth.backends.RemoteUserBackend` and
 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
 :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
 
 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
   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`
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 

+ 23 - 0
tests/auth_tests/test_remote_user.py

@@ -232,3 +232,26 @@ class CustomHeaderRemoteUserTest(RemoteUserTest):
         'auth_tests.test_remote_user.CustomHeaderMiddleware'
     )
     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')