Ticket #2507: ldapauth.diff

File ldapauth.diff, 10.4 KB (added by Marc Fargas <telenieko@…>, 17 years ago)

new patch, see notes on next comment.

  • contrib/ldapauth.py

     
     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    There are three ways to get the DN for a specific user.
     38        - You can set LDAP_BINDDN and LDAP_BIND_ATTRIBUTE like:
     39            LDAP_BINDDN = 'ou=people,dc=example,dc=com'
     40            LDAP_BIND_ATTRIBUTE = 'uid'
     41          and the user DN would be:
     42            uid=%s,ou=people,dc=example,dc=com
     43
     44        - Look for the DN on the directory, this is what will happen if you do
     45          not define the LDAP_BINDDN setting. In that case you will maybe want
     46          to define LDAP_PREBINDDN and LDAP_PREBINDPW if your LDAP server does
     47          not allow anonymous queries.
     48          For that search the LDAP_SEARCH_FILTER is needed.
     49
     50        - Override the _pre_bind() method, which receives the ldap object and
     51          the username as it's parameters and should return the DN of the user.
     52
     53    By inheriting this class you can change:
     54        - How the dn to bind with is produced by overriding _pre_bind()
     55        - What type of user object to use by overriding: _get_user_by_name(),
     56          _create_user_object(), and get_user()
     57
     58    See mk_pre_auth_bind.__doc__ for more information on overriding _pre_bind()
     59    """
     60
     61    import ldap
     62    from django.conf import settings
     63    from django.contrib.auth.models import User
     64
     65    settings = {
     66            'LDAP_SERVER_URI': 'ldap://localhost',
     67            'LDAP_SEARCHDN': 'dc=localhost',
     68            'LDAP_SCOPE': ldap.SCOPE_SUBTREE,
     69            'LDAP_SEARCH_FILTER': 'cn=%s',
     70            'LDAP_UPDATE_FIELDS': True,
     71            'LDAP_BINDDN': None,
     72            'LDAP_BIND_ATTRIBUTE': None,
     73            'LDAP_FIRST_NAME': None,
     74            'LDAP_LAST_NAME': None,
     75            'LDAP_FULL_NAME': None,
     76            'LDAP_GID': None,
     77            'LDAP_SU_GIDS': None,
     78            'LDAP_STAFF_GIDS': None,
     79            'LDAP_EMAIL': None,
     80            'LDAP_DEFAULT_EMAIL_SUFFIX': None,
     81            'LDAP_OPTIONS': None,
     82            'LDAP_BINDDN': None,
     83            'LDAP_BIND_ATTRIBUTE': None,
     84            'LDAP_PREBINDDN': None,
     85            'LDAP_PREBINDPW': None,
     86        }
     87
     88    def __init__(self):
     89        # Load settings from settings.py, put them on self.settings
     90        # overriding the defaults.
     91        for var in self.settings.iterkeys():
     92            if hasattr(settings, var):
     93                self.settings[var] = settings.__getattr__(var)
     94
     95    def authenticate(self, username=None, password=None):
     96        if not username and password is not None: # we need a user/pass
     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            # Failed user/pass
     113            l.unbind_s()
     114            return None
     115
     116        try:
     117            user = self._get_user_by_name(username)
     118        except User.DoesNotExist:
     119            user = self._get_ldap_user(l, username)
     120
     121        if user is not None:
     122            if self.settings['LDAP_UPDATE_FIELDS']:
     123                self._update_user(l, user)
     124
     125        l.unbind_s()
     126        return user
     127
     128    # Functions provided to override to customize to your LDAP configuration.
     129    def _pre_bind(self, l, username):
     130        """
     131        Function that returns the dn to bind against ldap with.
     132        called as: self._pre_bind(ldapobject, username)
     133        """
     134        if not self.settings['LDAP_BINDDN']:
     135            # When the LDAP_BINDDN setting is blank we try to find the
     136            # dn binding anonymously or using LDAP_PREBINDDN
     137            if self.settings['LDAP_PREBINDDN']:
     138                try:
     139                    l.simple_bind_s(self.settings['LDAP_PREBINDDN'],
     140                            self.settings['LDAP_PREVINDPW'])
     141                except ldap.LDAPError:
     142                    return None
     143
     144            # Now do the actual search
     145            filter = self.settings['LDAP_SEARCH_FILTER'] % username
     146            result = l.search_s(self.settings['LDAP_SEARCHDN'],
     147                        self.settings['LDAP_SCOPE'], filter, attrsonly=1)
     148
     149            if self.settings['LDAP_PREBINDDN']:
     150                l.unbind_s()
     151            if len(result) != 1:
     152                return None
     153            return result[0][0]
     154        else:
     155            # LDAP_BINDDN is set so we use it as a template.
     156            return "%s=%s,%s" % (self.settings['LDAP_BIND_ATTRIBUTE'], username,
     157                    self.settings['LDAP_BINDDN'])
     158   
     159    def _get_user_by_name(self, username):
     160        """
     161        Returns an object of contrib.auth.models.User that has a matching
     162        username.
     163        called as: self._get_user_by_name(username)
     164        """
     165        return User.objects.get(username=username)
     166
     167    def _create_user_object(self, username, password):
     168        """
     169        Creates and returns an object of contrib.auth.models.User.
     170        called as: self._create_user_object(username, password)
     171        """
     172        return User(username=username, password=password)
     173
     174    # Required for an authentication backend
     175    def get_user(self, user_id):
     176        try:
     177            return User.objects.get(pk=user_id)
     178        except:
     179            return None
     180    # End of functions to override
     181
     182    def _get_ldap_user(self, l, username):
     183        """
     184        Helper method, makes a user object and call update_user to populate
     185        """
     186
     187        # Generate a random password string.
     188        from random import choice
     189        import string
     190        password = ''.join([choice(string.printable) for i in range(10)])
     191
     192        user = self._create_user_object(username, password)
     193        return user
     194
     195    def _update_user(self, l, user):
     196        """
     197        Helper method, populates a user object with various attributes from
     198        LDAP.
     199        """
     200
     201        username = user.username
     202        filter = self.settings['LDAP_SEARCH_FILTER'] % username
     203
     204        # Get results of search and make sure something was found.
     205        # At this point this shouldn't fail.
     206        hold = l.search_s(self.settings['LDAP_SEARCHDN'],
     207                    self.settings['LDAP_SCOPE'], filter)
     208        if len(hold) < 1:
     209            raise AssertionError('No results found with: %s' % (filter))
     210
     211        dn = hold[0][0]
     212        attrs = hold[0][1]
     213        firstn = self.settings['LDAP_FIRST_NAME'] or None
     214        lastn = self.settings['LDAP_LAST_NAME'] or None
     215        emailf = self.settings['LDAP_EMAIL'] or None
     216
     217        if firstn:
     218            if firstn in attrs:
     219                user.first_name = attrs[firstn][0]
     220            else:
     221                raise NameError('Missing attribute: %s in result for %s'
     222                        % (firstn, dn))
     223        if lastn:
     224            if lastn in attrs:
     225                user.last_name = attrs[lastn][0]
     226            else:
     227                raise NameError('Missing attribute: %s in result for %s'
     228                        % (lastn, dn))
     229        if not firstn and not lastn and self.settings['LDAP_FULL_NAME']:
     230            fulln = self.settings['LDAP_FULL_NAME']
     231            if fulln in attrs:
     232                    tmp = attrs[fulln][0]
     233                    user.first_name = tmp.split(' ')[0]
     234                    user.last_name = ' '.join(tmp.split(' ')[1:])
     235            else:
     236                raise NameError('Missing attribute: %s in result for %s'
     237                        % (fulln, dn))
     238
     239        if emailf and emailf in attrs:
     240            user.email = attrs[emailf][0]
     241        elif self.settings['LDAP_DEFAULT_EMAIL_SUFFIX']:
     242            user.email = username + self.settings['LDAP_DEFAULT_EMAIL_SUFFIX']
     243
     244        if (hasattr(settings, 'LDAP_GID')
     245                and settings.LDAP_GID in attrs
     246                and hasattr(settings, 'LDAP_SU_GIDS')
     247                and attrs[settings.LDAP_GID][0] in settings.LDAP_SU_GIDS):
     248            user.is_superuser = True
     249            user.is_staff = True
     250        elif (hasattr(settings, 'LDAP_GID')
     251                and settings.LDAP_GID in attrs
     252                and hasattr(settings, 'LDAP_STAFF_GIDS')
     253                and attrs[settings.LDAP_GID][0] in settings.LDAP_STAFF_GIDS):
     254            user.is_superuser = False
     255            user.is_staff = True
     256        else:
     257            user.is_superuser = False
     258            user.is_staff = False
     259
     260        user.save()
     261
Back to Top