Index: django/contrib/auth/backends.py
===================================================================
--- django/contrib/auth/backends.py	(revision 3545)
+++ django/contrib/auth/backends.py	(working copy)
@@ -1,4 +1,5 @@
 from django.contrib.auth.models import User
+from django.conf import settings
 
 class ModelBackend:
     """
@@ -19,3 +20,192 @@
             return User.objects.get(pk=user_id)
         except User.DoesNotExist:
             return None
+
+class LDAPBackend(object):
+    """
+    Authenticate a user against LDAP.
+    Requires python-ldap to be installed.
+
+    Requires the following things to be in settings.py:
+    LDAP_BINDDN -- string of the LDAP dn to use for binding
+    LDAP_SEARCHDN -- string of the LDAP dn to use for searching
+    LDAP_BIND_ATTRIBUTE -- string of the LDAP attribute to use in binding
+        also used to search for a user.
+    LDAP_SERVER_URI -- string, ldap uri
+    LDAP_SCOPE -- one of: ldap.SCOPE_*, used for searching
+        ldap.SCOPE_BASE = 0
+        ldap.SCOPE_ONELEVEL = 1
+        ldap.SCOPE_SUBTREE = 2
+        see python-ldap docs for the search function
+    LDAP_UPDATE_FIELDS -- boolean, do we sync the db with ldap with each auth
+
+    Required unless LDAP_FULL_NAME is set:
+    LDAP_FIRST_NAME -- string, LDAP attribute to get the given name from
+    LDAP_LAST_NAME -- string, LDAP attribute to get the last name from
+
+    Optional Settings:
+    LDAP_FULL_NAME -- string, LDAP attribute to get name from, splits on ' '
+    LDAP_GID -- string, LDAP attribute to get group name/number from
+    LDAP_SU_GIDS -- list of strings, group names/numbers that are superusers
+    LDAP_STAFF_GIDS -- list of strings, group names/numbers that are staff
+    LDAP_EMAIL -- string, LDAP attribute to get email from
+    LDAP_DEFAULT_EMAIL_SUFFIX -- string, appened to username if no email found
+    LDAP_OPTIONS -- hash, python-ldap global options and their values
+        {ldap.OPT_X_TLS_CACERTDIR: '/etc/ldap/ca/'}
+    LDAP_BIND_STRING_FUNC -- Function to produce the string for binding the user
+        takes two arguments: the ldap object, a string username
+
+    How Binds Work:
+    LDAP_BINDDN = 'ou=people,dc=example,dc=com'
+    LDAP_BIND_ATTRIBUTE = 'uid'
+    # The bind would be performed via:
+    # uid=username,ou=people,dc=example,dc=com
+    """ 
+    import ldap
+
+    def authenticate(self, username=None, password=None):
+        if not username and password is not None: # we need a user/pass
+            l.unbind_s()
+            return None
+
+        if hasattr(settings, 'LDAP_OPTIONS'):
+            for k, v in settings.LDAP_OPTIONS:
+                ldap.set_option(k, v)
+
+        l = ldap.initialize(settings.LDAP_SERVER_URI)
+
+        if hasattr(settings, 'LDAP_BIND_STRING_FUNC'):
+            bind_string = settings.LDAP_BIND_STRING_FUNC(l, username)
+            if bind_string is None:
+                return None
+        else:
+            bind_string = "%s=%s,%s" % (settings.LDAP_BIND_ATTRIBUTE,
+                    username, settings.LDAP_BINDDN)
+        try:
+            l.bind_s(bind_string, password)
+        except ldap.INVALID_CREDENTIALS: # Failed user/pass
+            l.unbind_s()
+            return None
+
+        try:
+            user = User.objects.get(username=username)
+        except User.DoesNotExist:
+            user = None
+
+        if user is not None:
+            if settings.LDAP_UPDATE_FIELDS:
+                LDAPBackend.update_user(l, user)
+        else:
+            user = LDAPBackend.get_ldap_user(l, username)
+
+        l.unbind_s()
+        return user
+
+    def get_user(self, user_id):
+        try:
+            return User.objects.get(pk=user_id)
+        except:
+            return None
+
+    def get_ldap_user(l, username):
+        """
+        Helper method, makes a user object and call update_user to populate
+        """
+
+        user = User(username=username, password='Made by LDAP')
+        LDAPBackend.update_user(l, user)
+        return user
+    get_ldap_user = staticmethod(get_ldap_user)
+
+    def update_user(l, user):
+        """
+        Helper method, populates a user object with various attributes from
+        LDAP
+        """
+
+        username = user.username
+        filter_str = "%s=%s" % (settings.LDAP_BIND_ATTRIBUTE, username)
+        attrs = l.search_s(settings.LDAP_SEARCHDN, settings.LDAP_SCOPE,
+                filterstr=filter_str)[0][1]
+
+        if (hasattr(settings, 'LDAP_FIRST_NAME')
+                and hasattr(settings, 'LDAP_LAST_NAME')):
+            if (settings.LDAP_FIRST_NAME in attrs
+                    and settings.LDAP_LAST_NAME in attrs):
+                user.first_name = attrs[settings.LDAP_FIRST_NAME][0]
+                user.last_name = attrs[settings.LDAP_LAST_NAME][0]
+            else:
+                raise NameError('Missing needed fields %s or %s in LDAP'
+                        % (settings.LDAP_FIRST_NAME, settings.LDAP_LAST_NAME))
+        elif hasattr(settings, 'LDAP_FULL_NAME'):
+                if settings.LDAP_FULL_NAME in attrs:
+                    tmp = attrs[settings.FULL_NAME_FIELD][0]
+                    user.first_name = tmp.split(' ')[0]
+                    user.last_name = ' '.join(tmp.split(' ')[1:])
+                else:
+                    raise NameError('Required field %s missing in LDAP'
+                            % (settings.LDAP_FULL_NAME))
+        else:
+            raise NameError('Name fields not defined in settings.py')
+
+        if hasattr(settings, 'LDAP_EMAIL') and settings.LDAP_EMAIL in attrs:
+            user.email = attrs[settings.EMAIL_FIELD][0]
+        elif hasattr(settings, 'LDAP_DEFAULT_EMAIL_SUFFIX'):
+            user.email = username + settings.LDAP_DEFAULT_EMAIL_SUFFIX
+
+        if (hasattr(settings, 'LDAP_GID')
+                and settings.LDAP_GID in attrs 
+                and hasattr(settings, 'LDAP_SU_GIDS')
+                and attrs[settings.LDAP_GID][0] in settings.LDAP_SU_GIDS):
+            user.is_superuser = True
+            user.is_staff = True
+        elif (hasattr(settings, 'LDAP_GID')
+                and settings.LDAP_GID in attrs 
+                and hasattr(settings, 'LDAP_STAFF_GIDS')
+                and attrs[settings.LDAP_GID][0] in settings.LDAP_STAFF_GIDS):
+            user.is_superuser = False
+            user.is_staff = True
+        else:
+            user.is_superuser = False
+            user.is_staff = False
+
+        user.save()
+    update_user = staticmethod(update_user)
+
+    def mk_pre_auth_bind(auth_user, auth_pass, search_filter,
+            search_scope, search_base, search_attr=[]):
+        """
+        To be used for LDAP_BIND_STRING_FUNC.
+        Returns a function.
+        auth_user -- string, ldap formatted user to bind with
+        auth_pass -- string, password for auth_user
+        search_filter -- string, ldap search filter to use, %'d with username
+        search_scope -- ldap.SCOPE_* value, search scope
+        search_base -- string, base for the ldap search
+
+        optional:
+        search_attr -- list of strings, attributes to return
+            reduces traffic by limited the search results
+
+        set LDAP_BIND_STRING_FUNC like so:
+        LDAP_BIND_STRING_FUNC = mk_pre_auth_bind('dn=Me,dc=example,dc=com',
+                'pass', '(&(objectclass=person) (cn=%s))', ldap.SCOPE_SUBTREE,
+                'ou=people', ['dn'])
+        """
+        def pre_auth_bind(l, user): 
+            try: 
+                l.simple_bind_s(auth_user, auth_pass)
+            except ldap.LDAPError:
+                return None
+
+            filter = search_filter % user
+
+            result = l.search_s(search_base, search_scope, filter,
+                    search_attr)
+
+            if len(result) != 1:
+                return None
+
+            return result[0][0]
+        return pre_auth_bind
+    mk_pre_auth_bind = staticmethod(mk_pre_auth_bind)
