Ticket #689: 689_full.diff

File 689_full.diff, 9.9 KB (added by Marc Fargas, 12 years ago)

the code, the docs, the tests; And it works!

  • django/contrib/auth/backends.py

    diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py
    index f79929e..74d87f4 100644
    a b class ModelBackend: 
    6868            return User.objects.get(pk=user_id)
    6969        except User.DoesNotExist:
    7070            return None
     71
     72class RemoteUserAuthBackend(ModelBackend):
     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. password param is not used, just added in case :)
     83        """
     84        user = None
     85        if username:
     86            username = self.parse_user(username)
     87            try:
     88                user = User.objects.get(username=username)
     89            except User.DoesNotExist:
     90                user = self.unknown_user(username)
     91                user = self.configure_user(user)
     92        return user
     93
     94    def parse_user(self, username):
     95        """ Parse the provided username.
     96        Override this method if you need to do special things with the
     97        username, like stripping @realm or cleaning something like
     98        cn=x,dc=sas,etc.
     99        """
     100        return username
     101
     102    def unknown_user(self, username):
     103        # Auto-create user
     104        password = User.objects.make_random_password()
     105        user = User.objects.create_user(username, '', password)
     106        user.is_staff = False
     107        user.save()
     108        return user
     109
     110    def configure_user(self, user):
     111        """ Configure a user after login.
     112        i.e: to read group membership from LDAP and so on.
     113        """
     114        return user
     115
  • django/contrib/auth/middleware.py

    diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py
    index 42dc15a..cc8a9d8 100644
    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 --git a/django/contrib/auth/tests.py b/django/contrib/auth/tests.py
    index 329049c..30ed042 100644
    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 --git a/docs/auth_remote_user.txt b/docs/auth_remote_user.txt
    new file mode 100644
    index 0000000..57945e1
    - +  
     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 --git a/docs/authentication.txt b/docs/authentication.txt
    index 2c34c66..dd77ee1 100644
    a b plug in another authentication sources. You can override Django's default 
    966966database-based scheme, or you can use the default system in tandem with other
    967967systems.
    968968
     969There's a very specific situation/scenario in which you want to handle
     970authentication at the web server's level (i.e. standard HTTP AUTH) and want
     971Django to honour this authentication. This is covered in a separate page:
     972`Authenticating against REMOTE_USER from the Web Server`_
     973
     974.. _Authenticating against REMOTE_USER from the Web Server: ../auth_remote_user/
     975
    969976Specifying authentication backends
    970977----------------------------------
    971978
  • docs/request_response.txt

    diff --git a/docs/request_response.txt b/docs/request_response.txt
    index 8da00cd..e12f1f5 100644
    a b All attributes except ``session`` should be considered read-only. 
    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