Ticket #2507: backends.py.5.diff

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

Better handling of searches, and setting good defaults to mk_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_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)
Back to Top