Ticket #689: 689_full-3.diff

File 689_full-3.diff, 13.7 KB (added by Ramiro Morales, 16 years ago)

New version of the patch, incoporates ideas from several people for RemoteUserAuthBackend implementation

  • django/contrib/auth/backends.py

    diff -r 581e90af582f django/contrib/auth/backends.py
    a b from django.db import connection  
    11from django.db import connection
    22from django.contrib.auth.models import User
    33
    4 try: 
    5     set 
    6 except NameError: 
     4try:
     5    set
     6except NameError:
    77    from sets import Set as set # Python 2.3 fallback
    8        
     8
    99class ModelBackend:
    1010    """
    1111    Authenticate against django.contrib.auth.models.User
    class ModelBackend:  
    5050            cursor.execute(sql, [user_obj.id])
    5151            user_obj._group_perm_cache = set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()])
    5252        return user_obj._group_perm_cache
    53    
     53
    5454    def get_all_permissions(self, user_obj):
    5555        if not hasattr(user_obj, '_perm_cache'):
    5656            user_obj._perm_cache = set([u"%s.%s" % (p.content_type.app_label, p.codename) for p in user_obj.user_permissions.select_related()])
    class ModelBackend:  
    6868            return User.objects.get(pk=user_id)
    6969        except User.DoesNotExist:
    7070            return None
     71
     72class RemoteUserAuthBackend:
     73
     74    def __init__(self):
     75        if self.__class__ == RemoteUserAuthBackend:
     76            raise TypeError, "You must create your own class derived\
     77from Remote UserAuthBackend in order to use it."
     78
     79    def authenticate(self, username, password=None):
     80        """
     81        Authenticate user - RemoteUserAuth middleware passes REMOTE_USER
     82        as username.
     83        """
     84        if password is not None:
     85            return None
     86        user = None
     87        if username:
     88            username = self.parse_user(username)
     89            try:
     90                user = User.objects.get(username=username)
     91            except User.DoesNotExist:
     92                user = self.unknown_user(username)
     93                user = self.configure_user(user)
     94        return user
     95
     96    def parse_user(self, username):
     97        """ Parse the provided username.
     98        Override this method if you need to do special things with the
     99        username, like stripping @realm or cleaning something like
     100        cn=x,dc=sas,etc.
     101        """
     102        return username
     103
     104    def get_user(self, user_id):
     105        try:
     106            return User.objects.get(pk=user_id)
     107        except User.DoesNotExist:
     108            return None
     109
     110    def unknown_user(self, username):
     111        # Auto-create user
     112        user = User.objects.create_user(username, '')
     113        user.is_staff = False
     114        user.save()
     115        return user
     116
     117    def configure_user(self, user):
     118        """ Configure a user after login.
     119        i.e: to read group membership from LDAP and so on.
     120        """
     121        return user
     122
  • django/contrib/auth/middleware.py

    diff -r 581e90af582f django/contrib/auth/middleware.py
    a b class AuthenticationMiddleware(object):  
    1010        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'."
    1111        request.__class__.user = LazyUser()
    1212        return None
     13
     14class RemoteUserAuthMiddleware(object):
     15    def process_request(self, request):
     16        from django.contrib.auth import authenticate, login
     17        # AuthenticationMiddleware is required to create request.user
     18        error = """The Django RemoteUserAuth middleware requires authentication middleware to be installed. Edit your MIDDLEWARE_CLASSES
     19setting to insert 'django.contrib.auth.middleware.AuthenticationMiddleware' *before* the RemoteUserMiddleware class."""
     20        assert hasattr(request, 'user'), error
     21        if request.user.is_anonymous():
     22            user = None
     23            try:
     24                user = authenticate(username=request.META['REMOTE_USER'])
     25            except KeyError:
     26                pass # No remote user available
     27            if user is not None:
     28                request.user = user    # set request.user to the authenticated user
     29                login(request, user)   # auto-login the user to Django
     30        return None
  • django/contrib/auth/tests.py

    diff -r 581e90af582f django/contrib/auth/tests.py
    a b  
     1import os
     2import unittest
     3from django.contrib.auth.models import User
     4from django.contrib.auth.backends import RemoteUserAuthBackend
     5from django.test.client import Client
     6from django.conf import settings
     7
     8class SimpleDerivedBackend(RemoteUserAuthBackend):
     9    pass
     10
     11class HttpAuthTest(unittest.TestCase):
     12    def setUp(self):
     13        self.extra_headers = {'REMOTE_USER': 'iamnotanuser'}
     14        self.curr_middleware = settings.MIDDLEWARE_CLASSES
     15        self.curr_auth = settings.AUTHENTICATION_BACKENDS
     16
     17        settings.MIDDLEWARE_CLASSES +=\
     18            ('django.contrib.auth.middleware.RemoteUserAuthMiddleware', )
     19        settings.AUTHENTICATION_BACKENDS =\
     20            ('django.contrib.auth.tests.SimpleDerivedBackend',)
     21
     22    def testBackendMustBeDerived(self):
     23        """
     24        HttpAuthTest.testBackendMustBeDerived: RemoteUserAuthBackend cannot be
     25        used without being inherited by another class.
     26        """
     27        # RemoteUserAuthBackend cannot be instantiated!
     28        self.assertRaises(TypeError, RemoteUserAuthBackend)
     29
     30        # Check that it won't work on a request.
     31        settings.AUTHENTICATION_BACKENDS =\
     32            ('django.contrib.auth.backends.RemoteUserAuthBackend',)
     33        c = Client()
     34        self.assertRaises(TypeError, c.get ,'/', {}, **self.extra_headers)
     35       
     36    def testRemoteUserIsRespected(self):
     37        c = Client()
     38        extra_headers = {'REMOTE_USER': 'iamnotanuser'}
     39        res = c.get('/', {}, **self.extra_headers)
     40
     41        u = User.objects.get(username='iamnotanuser')
     42        # wow, the user was created! this works.
     43
     44    def tearDown(self):
     45        # Restore settings to avoid breaking other tests.
     46        settings.MIDDLEWARE_CLASSES = self.curr_middleware
     47        settings.AUTHENTICATION_BACKENDS = self.curr_auth
     48       
    149"""
    250>>> from models import User, AnonymousUser
    351>>> u = User.objects.create_user('testuser', 'test@example.com', 'testpw')
    False  
    2371[]
    2472>>> a.user_permissions.all()
    2573[]
    26 """
    27  No newline at end of file
     74"""
  • new file docs/auth_remote_user.txt

    diff -r 581e90af582f docs/auth_remote_user.txt
    - +  
     1======================================================
     2Authenticating against REMOTE_USER from the Web Server
     3======================================================
     4
     5Typically on intranet sites users are already authenticated (i.e. in a Windows
     6domain) by the web server (i.e. using IIS Integrated Authentication).
     7
     8When the web server takes care of authentication it sets the ``REMOTE_USER`` HTTP
     9header for use in the underlying application (i.e. Django). Then it's up to
     10this application take care of the authorization.
     11
     12Django brings all you need to make use of the ``REMOTE_USER`` header bringing you
     13one step furder to single sign-on on enterprise infrastucure!
     14
     15We assume that you have already configured your web server to authenticate
     16users, maybe with mod_auth_sspi in Apache, Integrated Authentication in IIS
     17and so on.
     18
     19Configuring Django
     20==================
     21
     22First of all, you must add the ``RemoteUserAuthMiddleware`` just **after**
     23(never before) ``AuthenticationMiddleware``.
     24
     25After this, you'll have to create you authentication backend that will take
     26care of checking that ``REMOTE_USER`` is valid. But don't be scared,
     27``RemoteUserAuthBackend`` is here to help you.
     28
     29``RemoteUserAuthBackend`` provides a "template" of what you need, you could
     30create a backend that simply inherits it and you are done. It will simply
     31assume that ``REMOTE_USER`` is always correct and create ``User``objects for
     32it.
     33
     34If you want more control, in you inherited authentication backend you can
     35override a few methods:
     36
     37   * ``parse_user``: Should cleanup ``REMOTE_USER`` (i.e. strip @realm from
     38     it). It takes the ``username`` as argument, and must return the cleaned
     39     ``username``.
     40   * ``unkown_user``: Should create and return a ``User`` object, will be
     41     called when a ``User`` object does not exist for ``REMOTE_USER``. Takes
     42     ``username`` as it's only argument.
     43   * ``configure_user``: Will be called after ``unkown_user`` so you can
     44     configure the recently created ``User`` object (in case you did not want
     45     to override ``unkown_user``. Takes the ``User`` instance as an argument.
     46     Should also return the ``User`` instance that represents the User.
     47
     48
     49Examples:
     50
     51    settings.py::
     52
     53        MIDDLEWARE_CLASSES = (
     54            'django.contrib.auth.middleware.AuthenticationMiddleware',
     55            'django.contrib.auth.middleware.RemoteUserAuthMiddleware',
     56            ...
     57            )
     58           
     59        AUTHENTICATION_BACKENDS = (
     60            'myproject.backends.MyDerivedBackend',
     61        )
     62
     63    myproject/backends.py::
     64
     65        from django.contrib.auth.backends import RemoteUserAuthBackend
     66
     67        class MyDerivedBackend(RemoteUserAuthBackend):
     68            # We don't really do anything, we are fine with the default
     69            # behaviour.
     70            pass
  • docs/authentication.txt

    diff -r 581e90af582f docs/authentication.txt
    a b This example shows how you might use bot  
    380380            # Return an 'invalid login' error message.
    381381
    382382.. admonition:: Calling ``authenticate()`` first
    383    
     383
    384384    When you're manually logging a user in, you *must* call
    385385    ``authenticate()`` before you call ``login()``; ``authenticate()``
    386386    sets an attribute on the ``User`` noting which authentication
    387387    backend successfully authenticated that user (see the `backends
    388388    documentation`_ for details), and this information is needed later
    389389    during the login process.
    390    
     390
    391391.. _backends documentation: #other-authentication-sources
    392392
    393393Manually checking a user's password
    introduced in Python 2.4::  
    460460
    461461In the Django development version, ``login_required`` also takes an optional
    462462``redirect_field_name`` parameter. Example::
    463    
     463
    464464    from django.contrib.auth.decorators import login_required
    465465
    466466    def my_view(request):
    In the Django development version, ``log  
    468468    my_view = login_required(redirect_field_name='redirect_to')(my_view)
    469469
    470470Again, an equivalent example of the more compact decorator syntax introduced in Python 2.4::
    471    
     471
    472472    from django.contrib.auth.decorators import login_required
    473473
    474474    @login_required(redirect_field_name='redirect_to')
    Again, an equivalent example of the more  
    479479
    480480    * If the user isn't logged in, redirect to ``settings.LOGIN_URL``
    481481      (``/accounts/login/`` by default), passing the current absolute URL
    482       in the query string as ``next`` or the value of ``redirect_field_name``. 
     482      in the query string as ``next`` or the value of ``redirect_field_name``.
    483483      For example:
    484484      ``/accounts/login/?next=/polls/3/``.
    485485    * If the user is logged in, execute the view normally. The view code is
    database-based scheme, or you can use th  
    10191019database-based scheme, or you can use the default system in tandem with other
    10201020systems.
    10211021
     1022.. admonition:: Handling authentication at the web server
     1023
     1024    There's a very specific situation/scenario in which you want to handle
     1025    authentication at the web server's level (i.e. standard HTTP AUTH) and want
     1026    Django to honour this authentication. This is covered in a separate page:
     1027    `Authenticating against REMOTE_USER from the Web Server`_
     1028
     1029.. _Authenticating against REMOTE_USER from the Web Server: ../auth_remote_user/
     1030
    10221031Specifying authentication backends
    10231032----------------------------------
    10241033
    Handling authorization in custom backend  
    11191128Handling authorization in custom backends
    11201129-----------------------------------------
    11211130
    1122 Custom auth backends can provide their own permissions. 
     1131Custom auth backends can provide their own permissions.
    11231132
    11241133The user model will delegate permission lookup functions
    11251134(``get_group_permissions()``, ``get_all_permissions()``, ``has_perm()``, and
    one backend grants.  
    11321141
    11331142The simple backend above could implement permissions for the magic admin fairly
    11341143simply::
    1135        
     1144
    11361145    class SettingsBackend:
    1137    
     1146
    11381147        # ...
    11391148
    11401149        def has_perm(self, user_obj, perm):
    simply::  
    11421151                return True
    11431152            else:
    11441153                return False
    1145                
     1154
    11461155This gives full permissions to the user granted access in the above example. Notice
    11471156that the backend auth functions all take the user object as an argument, and
    11481157they also accept the same arguments given to the associated ``User`` functions.
  • docs/request_response.txt

    diff -r 581e90af582f docs/request_response.txt
    a b All attributes except ``session`` should  
    109109        * ``QUERY_STRING`` -- The query string, as a single (unparsed) string.
    110110        * ``REMOTE_ADDR`` -- The IP address of the client.
    111111        * ``REMOTE_HOST`` -- The hostname of the client.
     112        * ``REMOTE_USER`` -- The user authenticated by the web server, if any.
    112113        * ``REQUEST_METHOD`` -- A string such as ``"GET"`` or ``"POST"``.
    113114        * ``SERVER_NAME`` -- The hostname of the server.
    114115        * ``SERVER_PORT`` -- The port of the server.
Back to Top