Django

Code

Ticket #689: 689.4.diff

File 689.4.diff, 20.6 kB (added by gwilson, 1 year ago)

add to docs paragraph about custom header value

  • django/contrib/auth/backends.py

    old new  
    7878            return User.objects.get(pk=user_id) 
    7979        except User.DoesNotExist: 
    8080            return None 
     81 
     82 
     83class RemoteUserBackend(ModelBackend): 
     84    """ 
     85    This backend is to be used in conjunction with the ``RemoteUserMiddleware`` 
     86    found in the middleware module of this package, and is used when the server 
     87    is handling authentication outside of Django. 
     88 
     89    By default, the ``authenticate`` method creates ``User`` objects for 
     90    usernames that don't already exist in the database.  Subclasses can disable 
     91    this behavior by setting the ``create_unknown_user`` attribute to 
     92    ``False``. 
     93    """ 
     94 
     95    # Create a User object if not already in the database? 
     96    create_unknown_user = True 
     97 
     98    def authenticate(self, remote_user): 
     99        """ 
     100        The username passed as ``remote_user`` is considered trusted.  This 
     101        method simply returns the ``User`` object with the given username, 
     102        creating a new ``User`` object if ``create_unknown_user`` is ``True``. 
     103 
     104        Returns None if ``create_unknown_user`` is ``False`` and a ``User`` 
     105        object with the given username is not found in the database. 
     106        """ 
     107        if not remote_user: 
     108            return 
     109        user = None 
     110        username = self.clean_username(remote_user) 
     111 
     112        # Note that this could be accomplished in one try-except clause, but 
     113        # instead we use get_or_create when creating unknown users since it has 
     114        # built-in safeguards for multiple threads. 
     115        if self.create_unknown_user: 
     116            user, created = User.objects.get_or_create(username=username) 
     117            if created: 
     118                user = self.configure_user(user) 
     119        else: 
     120            try: 
     121                user = User.objects.get(username=username) 
     122            except User.DoesNotExist: 
     123                pass 
     124        return user 
     125 
     126    def clean_username(self, username): 
     127        """ 
     128        Cleans the passed username to remove any extraneous text, and returns 
     129        the cleaned username. 
     130 
     131        By default, this method returns the passed username unmodified. 
     132        Override this method if you need to do special things with the 
     133        username, like stripping @realm or cleaning something like 
     134        cn=user,dc=domain. 
     135        """ 
     136        return username 
     137 
     138    def configure_user(self, user): 
     139        """ 
     140        Configures a user after creation and returns the updated user. 
     141 
     142        By default, this method returns the passed user unmodified.  Override 
     143        this method if you would like to do additional setup on the user that 
     144        was just created, such as set groups based on attributes in an LDAP 
     145        directory. 
     146        """ 
     147        return user 
  • django/contrib/auth/middleware.py

    old new  
     1from django.contrib import auth 
     2from django.core.exceptions import ImproperlyConfigured 
     3 
     4 
    15class LazyUser(object): 
    26    def __get__(self, request, obj_type=None): 
    37        if not hasattr(request, '_cached_user'): 
     
    59            request._cached_user = get_user(request) 
    610        return request._cached_user 
    711 
     12 
    813class AuthenticationMiddleware(object): 
    914    def process_request(self, request): 
    1015        assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'." 
    1116        request.__class__.user = LazyUser() 
    1217        return None 
     18 
     19 
     20class RemoteUserMiddleware(object): 
     21    """ 
     22    Middleware for utilizing web-server-provided authentication. 
     23 
     24    If request.user is not authenticated, then this middleware attempts to 
     25    authenticate the username passed in the ``REMOTE_USER`` request header. 
     26    If authentication is successful, the user is automatically logged in to 
     27    persist the user in the session. 
     28 
     29    The header used is configurable and defaults to ``REMOTE_USER``.  Subclass 
     30    this class and change the ``header`` attribute if you need to use a 
     31    different header. 
     32    """ 
     33 
     34    # Name of request header to grab username from.  This will be the key as 
     35    # used in the request.META dictionary, i.e. the normalization of headers to 
     36    # all uppercase and the addition of "HTTP_" prefix apply. 
     37    header = "REMOTE_USER" 
     38 
     39    def process_request(self, request): 
     40        # AuthenticationMiddleware is required so that request.user exists. 
     41        if not hasattr(request, 'user'): 
     42            raise ImproperlyConfigured( 
     43                "The Django remote user auth middleware requires the" 
     44                " authentication middleware to be installed.  Edit your" 
     45                " MIDDLEWARE_CLASSES setting to insert" 
     46                " 'django.contrib.auth.middleware.AuthenticationMiddleware'" 
     47                " before the RemoteUserMiddleware class.") 
     48        try: 
     49            username = request.META[self.header] 
     50        except KeyError: 
     51            # If specified header doesn't exist then return (leaving 
     52            # request.user set to AnonymousUser by the 
     53            # AuthenticationMiddleware). 
     54            return 
     55        # If the user is already authenticated and that user is the user we are 
     56        # getting passed in the headers, then the correct user is already 
     57        # persisted in the session and we don't need to continue. 
     58        if request.user.is_authenticated(): 
     59            if request.user.username == self.clean_username(username, request): 
     60                return 
     61        # We are seeing this user for the first time in this session, attempt 
     62        # to authenticate the user. 
     63        user = auth.authenticate(remote_user=username) 
     64        if user: 
     65            # User is valid.  Set request.user and persist user in the session 
     66            # by logging the user in. 
     67            request.user = user 
     68            auth.login(request, user) 
     69 
     70    def clean_username(self, username, request): 
     71        """ 
     72        Allows the backend to clean the username, if the backend defines a 
     73        clean_username method. 
     74        """ 
     75        backend_str = request.session[auth.BACKEND_SESSION_KEY] 
     76        backend = auth.load_backend(backend_str) 
     77        try: 
     78            username = backend.clean_username(username) 
     79        except AttributeError: # Backend has no clean_username method. 
     80            pass 
     81        return username 
  • django/contrib/auth/tests/__init__.py

    old new  
    11from django.contrib.auth.tests.basic import BASIC_TESTS 
    2 from django.contrib.auth.tests.views import PasswordResetTest, ChangePasswordTest 
     2from django.contrib.auth.tests.views \ 
     3        import PasswordResetTest, ChangePasswordTest 
    34from django.contrib.auth.tests.forms import FORM_TESTS 
     5from django.contrib.auth.tests.remote_user \ 
     6        import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest 
    47from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS 
    58 
    69# The password for the fixture data users is 'password' 
  • django/contrib/auth/tests/remote_user.py

    old new  
     1from datetime import datetime 
     2 
     3from django.conf import settings 
     4from django.contrib.auth.backends import RemoteUserBackend 
     5from django.contrib.auth.models import AnonymousUser, User 
     6from django.test import TestCase 
     7 
     8 
     9class RemoteUserTest(TestCase): 
     10 
     11    middleware = 'django.contrib.auth.middleware.RemoteUserMiddleware' 
     12    backend = 'django.contrib.auth.backends.RemoteUserBackend' 
     13    known_user = 'knownuser' 
     14 
     15    def setUp(self): 
     16        self.curr_middleware = settings.MIDDLEWARE_CLASSES 
     17        self.curr_auth = settings.AUTHENTICATION_BACKENDS 
     18        settings.MIDDLEWARE_CLASSES += (self.middleware,) 
     19        settings.AUTHENTICATION_BACKENDS = (self.backend,) 
     20 
     21    def test_no_remote_user(self): 
     22        """ 
     23        Tests requests where no remote user is specified and insures that no 
     24        users get created. 
     25        """ 
     26        num_users = User.objects.count() 
     27 
     28        response = self.client.get('/') 
     29        self.assert_(isinstance(response.context['user'], AnonymousUser)) 
     30        self.assertEqual(User.objects.count(), num_users) 
     31 
     32        response = self.client.get('/', REMOTE_USER=None) 
     33        self.assert_(isinstance(response.context['user'], AnonymousUser)) 
     34        self.assertEqual(User.objects.count(), num_users) 
     35 
     36        response = self.client.get('/', REMOTE_USER='') 
     37        self.assert_(isinstance(response.context['user'], AnonymousUser)) 
     38        self.assertEqual(User.objects.count(), num_users) 
     39 
     40    def test_unknown_user(self): 
     41        """ 
     42        Tests the case where the username passed in the header does not exist 
     43        as a User. 
     44        """ 
     45        num_users = User.objects.count() 
     46        response = self.client.get('/', REMOTE_USER='newuser') 
     47        self.assertEqual(response.context['user'].username, 'newuser') 
     48        self.assertEqual(User.objects.count(), num_users + 1) 
     49        User.objects.get(username='newuser') 
     50 
     51        # Another request with same user should not create any new users. 
     52        response = self.client.get('/', REMOTE_USER='newuser') 
     53        self.assertEqual(User.objects.count(), num_users + 1) 
     54 
     55    def test_known_user(self): 
     56        """ 
     57        Tests the case where the username passed in the header is a valid User. 
     58        """ 
     59        User.objects.create(username='knownuser') 
     60        User.objects.create(username='knownuser2') 
     61        num_users = User.objects.count() 
     62        response = self.client.get('/', REMOTE_USER='knownuser') 
     63        self.assertEqual(response.context['user'].username, 'knownuser') 
     64        self.assertEqual(User.objects.count(), num_users) 
     65        # Test that a different user passed in the headers causes the new user 
     66        # to be logged in. 
     67        response = self.client.get('/', REMOTE_USER='knownuser2') 
     68        self.assertEqual(response.context['user'].username, 'knownuser2') 
     69        self.assertEqual(User.objects.count(), num_users) 
     70 
     71    def test_last_login(self): 
     72        """ 
     73        Tests that a user's last_login is set the first time they make a 
     74        request but not updated in subsequent requests with the same session. 
     75        """ 
     76        user = User.objects.create(username='knownuser') 
     77        # Set last_login to something so we can determine if it changes. 
     78        default_login = datetime(2000, 1, 1) 
     79        user.last_login = default_login 
     80        user.save() 
     81 
     82        response = self.client.get('/', REMOTE_USER=self.known_user) 
     83        self.assertNotEqual(default_login, response.context['user'].last_login) 
     84 
     85        user = User.objects.get(username='knownuser') 
     86        user.last_login = default_login 
     87        user.save() 
     88        response = self.client.get('/', REMOTE_USER=self.known_user) 
     89        self.assertEqual(default_login, response.context['user'].last_login) 
     90 
     91    def tearDown(self): 
     92        """Restores settings to avoid breaking other tests.""" 
     93        settings.MIDDLEWARE_CLASSES = self.curr_middleware 
     94        settings.AUTHENTICATION_BACKENDS = self.curr_auth 
     95 
     96 
     97class RemoteUserNoCreateBackend(RemoteUserBackend): 
     98    """Backend that doesn't create unknown users.""" 
     99    create_unknown_user = False 
     100 
     101 
     102class RemoteUserNoCreateTest(RemoteUserTest): 
     103    """ 
     104    Contains the same tests as RemoteUserTest, but using a custom auth backend 
     105    class that doesn't create unknown users. 
     106    """ 
     107 
     108    backend =\ 
     109        'django.contrib.auth.tests.remote_user.RemoteUserNoCreateBackend' 
     110 
     111    def test_unknown_user(self): 
     112        num_users = User.objects.count() 
     113        response = self.client.get('/', REMOTE_USER='newuser') 
     114        self.assert_(isinstance(response.context['user'], AnonymousUser)) 
     115        self.assertEqual(User.objects.count(), num_users) 
     116 
     117 
     118class CustomRemoteUserBackend(RemoteUserBackend): 
     119    """ 
     120    Backend that overrides RemoteUserBackend methods. 
     121    """ 
     122 
     123    def clean_username(self, username): 
     124        """ 
     125        Grabs username before the @ character. 
     126        """ 
     127        return username.split('@')[0] 
     128 
     129    def configure_user(self, user): 
     130        """ 
     131        Sets user's email address. 
     132        """ 
     133        user.email = 'user@example.com' 
     134        user.save() 
     135        return user 
     136 
     137 
     138class RemoteUserCustomTest(RemoteUserTest): 
     139 
     140    backend =\ 
     141        'django.contrib.auth.tests.remote_user.CustomRemoteUserBackend' 
     142    known_user = 'knownuser@example.com' 
     143 
     144    def test_clean_username(self): 
     145        """ 
     146        The strings passed in REMOTE_USER should be cleaned and the known users 
     147        should not have been configured with an email address. 
     148        """ 
     149        User.objects.create(username='knownuser') 
     150        User.objects.create(username='knownuser2') 
     151        num_users = User.objects.count() 
     152 
     153        response = self.client.get('/', REMOTE_USER='knownuser@abc.com') 
     154        self.assertEqual(response.context['user'].username, 'knownuser') 
     155        self.assertEqual(response.context['user'].email, '') 
     156        self.assertEqual(User.objects.count(), num_users) 
     157 
     158        response = self.client.get('/', REMOTE_USER='knownuser2@abc.com') 
     159        self.assertEqual(response.context['user'].username, 'knownuser2') 
     160        self.assertEqual(response.context['user'].email, '') 
     161        self.assertEqual(User.objects.count(), num_users) 
     162 
     163    def test_unknown_user(self): 
     164        """ 
     165        The unknown user created should be configured with an email address. 
     166        """ 
     167        super(RemoteUserCustomTest, self).test_unknown_user() 
     168        newuser = User.objects.get(username='newuser') 
     169        self.assertEqual(newuser.email, 'user@example.com') 
  • docs/ref/authbackends.txt

    old new  
     1.. _ref-authentication-backends: 
     2 
     3========================================== 
     4Built-in authentication backends reference 
     5========================================== 
     6 
     7.. module:: django.contrib.auth.backends 
     8   :synopsis: Django's built-in authentication backend classes. 
     9 
     10This document details the authentication backends that come with Django. For 
     11information on how how to use them and how to write your own authentication 
     12backends, see the :ref:`Other authentication sources section 
     13<authentication-backends>` of the :ref:`User authentication guide 
     14<topics-auth>`. 
     15 
     16 
     17Available authentication backends 
     18================================= 
     19 
     20The following backends are available in :mod:`django.contrib.auth.backends`: 
     21 
     22.. class:: ModelBackend 
     23 
     24    This is the default authentication backend used by Django.  It 
     25    authenticates using usernames and passwords stored in the the 
     26    :class:`~django.contrib.auth.models.User` model. 
     27 
     28 
     29.. class:: RemoteUserBackend 
     30 
     31    .. versionadded:: 1.1 
     32 
     33    Use this backend to take advantage of external-to-Django-handled 
     34    authentication.  It authenticates using usernames passed in 
     35    :attr:`request.META['REMOTE_USER'] <django.http.HttpRequest.META>`.  See 
     36    the :ref:`Authenticating against REMOTE_USER <topics-auth-remote-user>` 
     37    documentation. 
  • docs/ref/index.txt

    old new  
    55 
    66.. toctree:: 
    77   :maxdepth: 1 
    8     
     8 
     9   authbackends 
    910   contrib/index 
    1011   databases 
    1112   django-admin 
     
    1920   signals 
    2021   templates/index 
    2122   unicode 
    22     
  • docs/topics/auth-remote-user.txt

    old new  
     1.. _topics-auth-remote-user: 
     2 
     3==================================== 
     4Authentication using ``REMOTE_USER`` 
     5==================================== 
     6 
     7This document describes how to make use of external authentication sources 
     8(where the Web server sets the ``REMOTE_USER`` environment variable) in your 
     9Django applications.  This type of authentication solution is typically seen on 
     10intranet sites, with single sign-on solutions such as IIS and Integrated 
     11Windows Authentication or Apache and `mod_authnz_ldap`_, `CAS`_, `Cosign`_, 
     12`WebAuth`_, `mod_auth_sspi`_, etc. 
     13 
     14.. _mod_authnz_ldap: http://httpd.apache.org/docs/2.2/mod/mod_authnz_ldap.html 
     15.. _CAS: http://www.ja-sig.org/products/cas/ 
     16.. _Cosign: http://weblogin.org 
     17.. _WebAuth: http://www.stanford.edu/services/webauth/ 
     18.. _mod_auth_sspi: http://sourceforge.net/projects/mod-auth-sspi 
     19 
     20When the Web server takes care of authentication it typically sets the 
     21``REMOTE_USER`` environment variable for use in the underlying application.  In 
     22Django, ``REMOTE_USER`` is made available in the :attr:`request.META 
     23<django.http.HttpRequest.META>` attribute.  Django can be configured to make 
     24use of the ``REMOTE_USER`` value using the ``RemoteUserMiddleware`` and 
     25``RemoteUserBackend`` classes found in :mod:`django.contirb.auth`. 
     26 
     27Configuration 
     28============= 
     29 
     30First, you must add the 
     31:class:`django.contrib.auth.middleware.RemoteUserMiddleware` to the 
     32:setting:`MIDDLEWARE_CLASSES` setting **after** the 
     33:class:`django.contrib.auth.middleware.AuthenticationMiddleware`:: 
     34 
     35    MIDDLEWARE_CLASSES = ( 
     36        ... 
     37        'django.contrib.auth.middleware.AuthenticationMiddleware', 
     38        'django.contrib.auth.middleware.RemoteUserMiddleware', 
     39        ... 
     40        ) 
     41 
     42Next, you must replace the :class:`~django.contrib.auth.backends.ModelBackend` 
     43with ``RemoteUserBackend`` in the :setting:`AUTHENTICATION_BACKENDS` setting:: 
     44 
     45    AUTHENTICATION_BACKENDS = ( 
     46        'django.contrib.auth.backends.RemoteUserBackend', 
     47    ) 
     48 
     49With this setup, ``RemoteUserMiddleware`` will detect the username in 
     50``request.META['REMOTE_USER']`` and will authenticate and auto-login that user 
     51using the ``RemoteUserBackend``. 
     52 
     53.. note:: 
     54   Since the ``RemoteUserBackend`` inherits from ``ModelBackend``, you will 
     55   still have all of the same permissions checking that is implemented in 
     56   ``ModelBackend``. 
     57 
     58If your authentication mechanism uses a custom HTTP header and not 
     59``REMOTE_USER``, you can subclass ``RemoteUserMiddleware`` and set the 
     60``header`` attribute to the desired ``request.META`` key.  For example:: 
     61 
     62    from django.contrib.auth.middleware import RemoteUserMiddleware 
     63 
     64    class CustomHeaderMiddleware(RemoteUserMiddleware): 
     65        header = 'HTTP_AUTHUSER' 
     66 
     67 
     68``RemoteUserBackend`` 
     69===================== 
     70 
     71.. class:: django.contrib.backends.RemoteUserBackend 
     72 
     73If you need more control, you can create your own authentication backend 
     74that inherits from ``RemoteUserBackend`` and overrides certain parts: 
     75 
     76Attributes 
     77~~~~~~~~~~ 
     78 
     79.. attribute:: RemoteUserBackend.create_unknown_user 
     80 
     81    ``True`` or ``False``.  Determines whether or not a 
     82    :class:`~django.contrib.auth.models.User` object is created if not already 
     83    in the database.  Defaults to ``True``. 
     84 
     85Methods 
     86~~~~~~~ 
     87 
     88.. method:: RemoteUserBackend.clean_username(username) 
     89 
     90   Clean unwanted text from the passed username.  This is useful if the 
     91   username includes unwanted text, such extra LDAP DN information.  This 
     92   method needs to return the cleaned username. 
     93 
     94.. method:: RemoteUserBackend.configure_user(user) 
     95 
     96   Configure a newly created user.  This method is called immediately after a 
     97   new user is created, and can be used to perform custom setup actions for the 
     98   user.  This methods needs to return the user object. 
  • docs/topics/auth.txt

    old new  
    12631263and the Django-based applications. 
    12641264 
    12651265So, to handle situations like this, the Django authentication system lets you 
    1266 plug in another authentication sources. You can override Django's default 
     1266plug in other authentication sources. You can override Django's default 
    12671267database-based scheme, or you can use the default system in tandem with other 
    12681268systems. 
    12691269 
     1270See the :ref:`authentication backend reference <ref-authentication-backends>` 
     1271for information on the authentication backends included with Django. 
     1272 
    12701273Specifying authentication backends 
    12711274---------------------------------- 
    12721275 
  • docs/topics/index.txt

    old new  
    1717   files 
    1818   testing 
    1919   auth 
     20   auth-remote-user 
    2021   cache 
    2122   email 
    2223   i18n