Django

Code

Ticket #2507: backends.py.5.diff

File backends.py.5.diff, 8.3 kB (added by spr@mahonri5.net, 2 years ago)

Better handling of searches, and setting good defaults to mk_pre_auth_bind()

  • django/contrib/auth/backends.py

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