Ticket #2507: ldapauth.3.diff

File ldapauth.3.diff, 11.0 KB (added by bugs@…, 16 years ago)

changed random password generation, added is_active fields.

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