Django

Code

Ticket #2507: ldapauth.1.diff

File ldapauth.1.diff, 10.6 kB (added by Scott Paul Robertson <spr@mahonri5.net>, 1 year ago)

Patch with one bug fix and some documentation improvements.

  • django/contrib/auth/contrib/ldapauth.py

    old new  
     1from django.conf import settings 
     2from django.contrib.auth.models import User 
     3 
     4class LDAPBackend(object): 
     5    """ 
     6    Authenticate a user against LDAP. 
     7    Requires python-ldap to be installed. 
     8 
     9    Requires the following things to be in settings.py: 
     10    LDAP_SERVER_URI -- string, ldap uri. 
     11        default: 'ldap://localhost' 
     12    LDAP_SEARCHDN -- string of the LDAP dn to use for searching 
     13        default: 'dc=localhost' 
     14    LDAP_SCOPE -- one of: ldap.SCOPE_*, used for searching 
     15        see python-ldap docs for the search function 
     16        default = ldap.SCOPE_SUBTREE 
     17    LDAP_SEARCH_FILTER -- formated string, the filter to use for searching for a 
     18        user. Used as: filterstr = LDAP_SEARCH_FILTER % username 
     19        default = 'cn=%s' 
     20    LDAP_UPDATE_FIELDS -- boolean, do we sync the db with ldap on each auth 
     21        default = True 
     22 
     23    Required unless LDAP_FULL_NAME is set: 
     24    LDAP_FIRST_NAME -- string, LDAP attribute to get the given name from 
     25    LDAP_LAST_NAME -- string, LDAP attribute to get the last name from 
     26 
     27    Optional Settings: 
     28    LDAP_FULL_NAME -- string, LDAP attribute to get name from, splits on ' ' 
     29    LDAP_GID -- string, LDAP attribute to get group name/number from 
     30    LDAP_SU_GIDS -- list of strings, group names/numbers that are superusers 
     31    LDAP_STAFF_GIDS -- list of strings, group names/numbers that are staff 
     32    LDAP_EMAIL -- string, LDAP attribute to get email from 
     33    LDAP_DEFAULT_EMAIL_SUFFIX -- string, appened to username if no email found 
     34    LDAP_OPTIONS -- hash, python-ldap global options and their values 
     35        {ldap.OPT_X_TLS_CACERTDIR: '/etc/ldap/ca/'} 
     36 
     37    You must pick a method for determining the DN of a user and set the needed 
     38    settings: 
     39        - You can set LDAP_BINDDN and LDAP_BIND_ATTRIBUTE like: 
     40            LDAP_BINDDN = 'ou=people,dc=example,dc=com' 
     41            LDAP_BIND_ATTRIBUTE = 'uid' 
     42          and the user DN would be: 
     43            'uid=%s,ou=people,dc=example,dc=com' % username 
     44 
     45        - Look for the DN on the directory, this is what will happen if you do 
     46          not define the LDAP_BINDDN setting. In that case you may need to 
     47          define LDAP_PREBINDDN and LDAP_PREBINDPW if your LDAP server does not 
     48          allow anonymous queries. The search will be performed with the 
     49          LDAP_SEARCH_FILTER setting. 
     50 
     51        - Override the _pre_bind() method, which receives the ldap object and 
     52          the username as it's parameters and should return the DN of the user. 
     53 
     54    By inheriting this class you can change: 
     55        - How the dn to bind with is produced by overriding _pre_bind() 
     56        - What type of user object to use by overriding: _get_user_by_name(), 
     57          _create_user_object(), and get_user() 
     58    """  
     59 
     60    import ldap 
     61    from django.conf import settings 
     62    from django.contrib.auth.models import User 
     63 
     64    settings = { 
     65            'LDAP_SERVER_URI': 'ldap://localhost', 
     66            'LDAP_SEARCHDN': 'dc=localhost', 
     67            'LDAP_SCOPE': ldap.SCOPE_SUBTREE, 
     68            'LDAP_SEARCH_FILTER': 'cn=%s', 
     69            'LDAP_UPDATE_FIELDS': True, 
     70            'LDAP_BINDDN': None, 
     71            'LDAP_BIND_ATTRIBUTE': None, 
     72            'LDAP_FIRST_NAME': None, 
     73            'LDAP_LAST_NAME': None, 
     74            'LDAP_FULL_NAME': None, 
     75            'LDAP_GID': None, 
     76            'LDAP_SU_GIDS': None, 
     77            'LDAP_STAFF_GIDS': None, 
     78            'LDAP_EMAIL': None, 
     79            'LDAP_DEFAULT_EMAIL_SUFFIX': None, 
     80            'LDAP_OPTIONS': None, 
     81            'LDAP_BINDDN': None, 
     82            'LDAP_BIND_ATTRIBUTE': None, 
     83            'LDAP_PREBINDDN': None, 
     84            'LDAP_PREBINDPW': None, 
     85        } 
     86 
     87    def __init__(self): 
     88        # Load settings from settings.py, put them on self.settings 
     89        # overriding the defaults. 
     90        for var in self.settings.iterkeys(): 
     91            if hasattr(settings, var): 
     92                self.settings[var] = settings.__getattr__(var) 
     93 
     94    def authenticate(self, username=None, password=None): 
     95        # Make sure we have a user and pass 
     96        if not username and password is not None: 
     97            return None 
     98 
     99        if self.settings['LDAP_OPTIONS']: 
     100            for k in self.settings['LDAP_OPTIONS']: 
     101                self.ldap.set_option(k, self.settings.LDAP_OPTIONS[k]) 
     102 
     103        l = self.ldap.initialize(self.settings['LDAP_SERVER_URI']) 
     104 
     105        bind_string = self._pre_bind(l, username) 
     106 
     107        try: 
     108            # Try to bind as the provided user. We leave the bind until 
     109            # the end for other ldap.search_s call to work authenticated. 
     110            l.bind_s(bind_string, password) 
     111        except (self.ldap.INVALID_CREDENTIALS,  
     112                self.ldap.UNWILLING_TO_PERFORM): 
     113            # Failed user/pass (or missing password) 
     114            l.unbind_s() 
     115            return None 
     116 
     117        try: 
     118            user = self._get_user_by_name(username) 
     119        except User.DoesNotExist: 
     120            user = self._get_ldap_user(l, username) 
     121 
     122        if user is not None: 
     123            if self.settings['LDAP_UPDATE_FIELDS']: 
     124                self._update_user(l, user) 
     125 
     126        l.unbind_s() 
     127        return user 
     128 
     129    # Functions provided to override to customize to your LDAP configuration. 
     130    def _pre_bind(self, l, username): 
     131        """ 
     132        Function that returns the dn to bind against ldap with. 
     133        called as: self._pre_bind(ldapobject, username) 
     134        """ 
     135        if not self.settings['LDAP_BINDDN']: 
     136            # When the LDAP_BINDDN setting is blank we try to find the 
     137            # dn binding anonymously or using LDAP_PREBINDDN 
     138            if self.settings['LDAP_PREBINDDN']: 
     139                try: 
     140                    l.simple_bind_s(self.settings['LDAP_PREBINDDN'], 
     141                            self.settings['LDAP_PREVINDPW']) 
     142                except ldap.LDAPError: 
     143                    return None 
     144 
     145            # Now do the actual search 
     146            filter = self.settings['LDAP_SEARCH_FILTER'] % username 
     147            result = l.search_s(self.settings['LDAP_SEARCHDN'], 
     148                        self.settings['LDAP_SCOPE'], filter, attrsonly=1) 
     149 
     150            if self.settings['LDAP_PREBINDDN']: 
     151                l.unbind_s() 
     152            if len(result) != 1: 
     153                return None 
     154            return result[0][0] 
     155        else: 
     156            # LDAP_BINDDN is set so we use it as a template. 
     157            return "%s=%s,%s" % (self.settings['LDAP_BIND_ATTRIBUTE'], username, 
     158                    self.settings['LDAP_BINDDN']) 
     159     
     160    def _get_user_by_name(self, username): 
     161        """ 
     162        Returns an object of contrib.auth.models.User that has a matching 
     163        username. 
     164        called as: self._get_user_by_name(username) 
     165        """ 
     166        return User.objects.get(username=username) 
     167 
     168    def _create_user_object(self, username, password): 
     169        """ 
     170        Creates and returns an object of contrib.auth.models.User. 
     171        called as: self._create_user_object(username, password) 
     172        """ 
     173        return User(username=username, password=password) 
     174 
     175    # Required for an authentication backend 
     176    def get_user(self, user_id): 
     177        try: 
     178            return User.objects.get(pk=user_id) 
     179        except: 
     180            return None 
     181    # End of functions to override 
     182 
     183    def _get_ldap_user(self, l, username): 
     184        """ 
     185        Helper method, makes a user object and call update_user to populate 
     186        """ 
     187 
     188        # Generate a random password string. 
     189        from random import choice 
     190        import string 
     191        password = ''.join([choice(string.printable) for i in range(10)]) 
     192 
     193        user = self._create_user_object(username, password) 
     194        return user 
     195 
     196    def _update_user(self, l, user): 
     197        """ 
     198        Helper method, populates a user object with various attributes from 
     199        LDAP. 
     200        """ 
     201 
     202        username = user.username 
     203        filter = self.settings['LDAP_SEARCH_FILTER'] % username 
     204 
     205        # Get results of search and make sure something was found. 
     206        # At this point this shouldn't fail. 
     207        hold = l.search_s(self.settings['LDAP_SEARCHDN'], 
     208                    self.settings['LDAP_SCOPE'], filter) 
     209        if len(hold) < 1: 
     210            raise AssertionError('No results found with: %s' % (filter)) 
     211 
     212        dn = hold[0][0] 
     213        attrs = hold[0][1] 
     214        firstn = self.settings['LDAP_FIRST_NAME'] or None 
     215        lastn = self.settings['LDAP_LAST_NAME'] or None 
     216        emailf = self.settings['LDAP_EMAIL'] or None 
     217 
     218        if firstn: 
     219            if firstn in attrs: 
     220                user.first_name = attrs[firstn][0] 
     221            else: 
     222                raise NameError('Missing attribute: %s in result for %s'  
     223                        % (firstn, dn)) 
     224        if lastn: 
     225            if lastn in attrs: 
     226                user.last_name = attrs[lastn][0] 
     227            else: 
     228                raise NameError('Missing attribute: %s in result for %s'  
     229                        % (lastn, dn)) 
     230        if not firstn and not lastn and self.settings['LDAP_FULL_NAME']: 
     231            fulln = self.settings['LDAP_FULL_NAME'] 
     232            if fulln in attrs: 
     233                    tmp = attrs[fulln][0] 
     234                    user.first_name = tmp.split(' ')[0] 
     235                    user.last_name = ' '.join(tmp.split(' ')[1:]) 
     236            else: 
     237                raise NameError('Missing attribute: %s in result for %s'  
     238                        % (fulln, dn)) 
     239 
     240        if emailf and emailf in attrs: 
     241            user.email = attrs[emailf][0] 
     242        elif self.settings['LDAP_DEFAULT_EMAIL_SUFFIX']: 
     243            user.email = username + self.settings['LDAP_DEFAULT_EMAIL_SUFFIX'] 
     244 
     245        if (hasattr(settings, 'LDAP_GID') 
     246                and settings.LDAP_GID in attrs 
     247                and hasattr(settings, 'LDAP_SU_GIDS') 
     248                and attrs[settings.LDAP_GID][0] in settings.LDAP_SU_GIDS): 
     249            user.is_superuser = True 
     250            user.is_staff = True 
     251        elif (hasattr(settings, 'LDAP_GID') 
     252                and settings.LDAP_GID in attrs 
     253                and hasattr(settings, 'LDAP_STAFF_GIDS') 
     254                and attrs[settings.LDAP_GID][0] in settings.LDAP_STAFF_GIDS): 
     255            user.is_superuser = False 
     256            user.is_staff = True 
     257        else: 
     258            user.is_superuser = False 
     259            user.is_staff = False 
     260 
     261        user.save() 
     262