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

This commit is contained in:
Jan Pazdziora 2015-06-26 20:59:57 +02:00 committed by Tim Graham
parent c6cce4de38
commit a570701e02
6 changed files with 75 additions and 2 deletions

View File

@ -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/>

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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`
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -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')