Ticket #2507: backends.py.4.diff

File backends.py.4.diff, 7.8 KB (added by spr@…, 18 years ago)

Slight update to make the users experience better with the pre_auth_bind.

  • django/contrib/auth/backends.py

     
    11from django.contrib.auth.models import User
     2from django.conf import settings
    23
    34class ModelBackend:
    45    """
     
    1920            return User.objects.get(pk=user_id)
    2021        except User.DoesNotExist:
    2122            return None
     23
     24class LDAPBackend(object):
     25    """
     26    Authenticate a user against LDAP.
     27    Requires python-ldap to be installed.
     28
     29    Requires the following things to be in settings.py:
     30    LDAP_BINDDN -- string of the LDAP dn to use for binding
     31    LDAP_SEARCHDN -- string of the LDAP dn to use for searching
     32    LDAP_BIND_ATTRIBUTE -- string of the LDAP attribute to use in binding
     33        also used to search for a user.
     34    LDAP_SERVER_URI -- string, ldap uri
     35    LDAP_SCOPE -- one of: ldap.SCOPE_*, used for searching
     36        ldap.SCOPE_BASE = 0
     37        ldap.SCOPE_ONELEVEL = 1
     38        ldap.SCOPE_SUBTREE = 2
     39        see python-ldap docs for the search function
     40    LDAP_UPDATE_FIELDS -- boolean, do we sync the db with ldap with each auth
     41
     42    Required unless LDAP_FULL_NAME is set:
     43    LDAP_FIRST_NAME -- string, LDAP attribute to get the given name from
     44    LDAP_LAST_NAME -- string, LDAP attribute to get the last name from
     45
     46    Optional Settings:
     47    LDAP_FULL_NAME -- string, LDAP attribute to get name from, splits on ' '
     48    LDAP_GID -- string, LDAP attribute to get group name/number from
     49    LDAP_SU_GIDS -- list of strings, group names/numbers that are superusers
     50    LDAP_STAFF_GIDS -- list of strings, group names/numbers that are staff
     51    LDAP_EMAIL -- string, LDAP attribute to get email from
     52    LDAP_DEFAULT_EMAIL_SUFFIX -- string, appened to username if no email found
     53    LDAP_OPTIONS -- hash, python-ldap global options and their values
     54        {ldap.OPT_X_TLS_CACERTDIR: '/etc/ldap/ca/'}
     55    LDAP_BIND_STRING_FUNC -- Function to produce the string for binding the user
     56        takes two arguments: the ldap object, a string username
     57
     58    How Binds Work:
     59    LDAP_BINDDN = 'ou=people,dc=example,dc=com'
     60    LDAP_BIND_ATTRIBUTE = 'uid'
     61    # The bind would be performed via:
     62    # uid=username,ou=people,dc=example,dc=com
     63    """
     64    import ldap
     65
     66    def authenticate(self, username=None, password=None):
     67        if not username and password is not None: # we need a user/pass
     68            l.unbind_s()
     69            return None
     70
     71        if hasattr(settings, 'LDAP_OPTIONS'):
     72            for k, v in settings.LDAP_OPTIONS:
     73                ldap.set_option(k, v)
     74
     75        l = ldap.initialize(settings.LDAP_SERVER_URI)
     76
     77        if hasattr(settings, 'LDAP_BIND_STRING_FUNC'):
     78            bind_string = settings.LDAP_BIND_STRING_FUNC(l, username)
     79            if bind_string is None:
     80                return None
     81        else:
     82            bind_string = "%s=%s,%s" % (settings.LDAP_BIND_ATTRIBUTE,
     83                    username, settings.LDAP_BINDDN)
     84        try:
     85            l.bind_s(bind_string, password)
     86        except ldap.INVALID_CREDENTIALS: # Failed user/pass
     87            l.unbind_s()
     88            return None
     89
     90        try:
     91            user = User.objects.get(username=username)
     92        except User.DoesNotExist:
     93            user = None
     94
     95        if user is not None:
     96            if settings.LDAP_UPDATE_FIELDS:
     97                LDAPBackend.update_user(l, user)
     98        else:
     99            user = LDAPBackend.get_ldap_user(l, username)
     100
     101        l.unbind_s()
     102        return user
     103
     104    def get_user(self, user_id):
     105        try:
     106            return User.objects.get(pk=user_id)
     107        except:
     108            return None
     109
     110    def get_ldap_user(l, username):
     111        """
     112        Helper method, makes a user object and call update_user to populate
     113        """
     114
     115        user = User(username=username, password='Made by LDAP')
     116        LDAPBackend.update_user(l, user)
     117        return user
     118    get_ldap_user = staticmethod(get_ldap_user)
     119
     120    def update_user(l, user):
     121        """
     122        Helper method, populates a user object with various attributes from
     123        LDAP
     124        """
     125
     126        username = user.username
     127        filter_str = "%s=%s" % (settings.LDAP_BIND_ATTRIBUTE, username)
     128        attrs = l.search_s(settings.LDAP_SEARCHDN, settings.LDAP_SCOPE,
     129                filterstr=filter_str)[0][1]
     130
     131        if (hasattr(settings, 'LDAP_FIRST_NAME')
     132                and hasattr(settings, 'LDAP_LAST_NAME')):
     133            if (settings.LDAP_FIRST_NAME in attrs
     134                    and settings.LDAP_LAST_NAME in attrs):
     135                user.first_name = attrs[settings.LDAP_FIRST_NAME][0]
     136                user.last_name = attrs[settings.LDAP_LAST_NAME][0]
     137            else:
     138                raise NameError('Missing needed fields %s or %s in LDAP'
     139                        % (settings.LDAP_FIRST_NAME, settings.LDAP_LAST_NAME))
     140        elif hasattr(settings, 'LDAP_FULL_NAME'):
     141                if settings.LDAP_FULL_NAME in attrs:
     142                    tmp = attrs[settings.FULL_NAME_FIELD][0]
     143                    user.first_name = tmp.split(' ')[0]
     144                    user.last_name = ' '.join(tmp.split(' ')[1:])
     145                else:
     146                    raise NameError('Required field %s missing in LDAP'
     147                            % (settings.LDAP_FULL_NAME))
     148        else:
     149            raise NameError('Name fields not defined in settings.py')
     150
     151        if hasattr(settings, 'LDAP_EMAIL') and settings.LDAP_EMAIL in attrs:
     152            user.email = attrs[settings.EMAIL_FIELD][0]
     153        elif hasattr(settings, 'LDAP_DEFAULT_EMAIL_SUFFIX'):
     154            user.email = username + settings.LDAP_DEFAULT_EMAIL_SUFFIX
     155
     156        if (hasattr(settings, 'LDAP_GID')
     157                and settings.LDAP_GID in attrs
     158                and hasattr(settings, 'LDAP_SU_GIDS')
     159                and attrs[settings.LDAP_GID][0] in settings.LDAP_SU_GIDS):
     160            user.is_superuser = True
     161            user.is_staff = True
     162        elif (hasattr(settings, 'LDAP_GID')
     163                and settings.LDAP_GID in attrs
     164                and hasattr(settings, 'LDAP_STAFF_GIDS')
     165                and attrs[settings.LDAP_GID][0] in settings.LDAP_STAFF_GIDS):
     166            user.is_superuser = False
     167            user.is_staff = True
     168        else:
     169            user.is_superuser = False
     170            user.is_staff = False
     171
     172        user.save()
     173    update_user = staticmethod(update_user)
     174
     175    def mk_pre_auth_bind(auth_user, auth_pass, search_filter,
     176            search_scope, search_base, search_attr=[]):
     177        """
     178        To be used for LDAP_BIND_STRING_FUNC.
     179        Returns a function.
     180        auth_user -- string, ldap formatted user to bind with
     181        auth_pass -- string, password for auth_user
     182        search_filter -- string, ldap search filter to use, %'d with username
     183        search_scope -- ldap.SCOPE_* value, search scope
     184        search_base -- string, base for the ldap search
     185
     186        optional:
     187        search_attr -- list of strings, attributes to return
     188            reduces traffic by limited the search results
     189
     190        set LDAP_BIND_STRING_FUNC like so:
     191        LDAP_BIND_STRING_FUNC = mk_pre_auth_bind('dn=Me,dc=example,dc=com',
     192                'pass', '(&(objectclass=person) (cn=%s))', ldap.SCOPE_SUBTREE,
     193                'ou=people', ['dn'])
     194        """
     195        def pre_auth_bind(l, user):
     196            try:
     197                l.simple_bind_s(auth_user, auth_pass)
     198            except ldap.LDAPError:
     199                return None
     200
     201            filter = search_filter % user
     202
     203            result = l.search_s(search_base, search_scope, filter,
     204                    search_attr)
     205
     206            if len(result) != 1:
     207                return None
     208
     209            return result[0][0]
     210        return pre_auth_bind
     211    mk_pre_auth_bind = staticmethod(mk_pre_auth_bind)
Back to Top