Django

Code

Ticket #2507: ldap.3.diff

File ldap.3.diff, 10.2 kB (added by spr@mahonri5.net, 2 years ago)

Fixes a few run-time bugs, adding some more error checking.

  • django/conf/global_settings.py

    old new  
    296296################## 
    297297 
    298298AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',) 
     299 
     300# Defaults for LDAPBackend 
     301LDAP_SERVER_URI = 'ldap://localhost' 
     302LDAP_SEARCHDN = 'dc=localhost' 
     303LDAP_SCOPE = 2  # ldap.SCOPE_SUBTREE 
     304LDAP_SEARCH_FILTER = 'cn=%s' 
     305LDAP_UPDATE_FIELDS = True 
     306LDAP_BIND_DN = 'dc=localhost' 
     307LDAP_BIND_ATTRIBUTE = 'dn' 
  • 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_SERVER_URI -- string, ldap uri. 
     31        default: 'ldap://localhost' 
     32    LDAP_SEARCHDN -- string of the LDAP dn to use for searching 
     33        default: 'dc=localhost' 
     34    LDAP_SCOPE -- one of: ldap.SCOPE_*, used for searching 
     35        ldap.SCOPE_BASE = 0 
     36        ldap.SCOPE_ONELEVEL = 1 
     37        ldap.SCOPE_SUBTREE = 2 
     38        see python-ldap docs for the search function 
     39        default = ldap.SCOPE_SUBTREE 
     40    LDAP_SEARCH_FILTER -- formated string, the filter to use for searching for a 
     41        user. Used as: filterstr = LDAP_SEARCH_FILTER % username 
     42        default = 'cn=%s' 
     43    LDAP_UPDATE_FIELDS -- boolean, do we sync the db with ldap on each auth 
     44        default = True 
     45 
     46    Bind Options (Required unless _pre_bind() is overridden): 
     47    LDAP_BINDDN -- string of the LDAP dn to use for binding 
     48    LDAP_BIND_ATTRIBUTE -- string of the LDAP attribute to use in binding. 
     49 
     50    Required unless LDAP_FULL_NAME is set: 
     51    LDAP_FIRST_NAME -- string, LDAP attribute to get the given name from 
     52    LDAP_LAST_NAME -- string, LDAP attribute to get the last name from 
     53 
     54    Optional Settings: 
     55    LDAP_FULL_NAME -- string, LDAP attribute to get name from, splits on ' ' 
     56    LDAP_GID -- string, LDAP attribute to get group name/number from 
     57    LDAP_SU_GIDS -- list of strings, group names/numbers that are superusers 
     58    LDAP_STAFF_GIDS -- list of strings, group names/numbers that are staff 
     59    LDAP_EMAIL -- string, LDAP attribute to get email from 
     60    LDAP_DEFAULT_EMAIL_SUFFIX -- string, appened to username if no email found 
     61    LDAP_OPTIONS -- hash, python-ldap global options and their values 
     62        {ldap.OPT_X_TLS_CACERTDIR: '/etc/ldap/ca/'} 
     63 
     64    How binding is done: 
     65    LDAP_BINDDN = 'ou=people,dc=example,dc=com' 
     66    LDAP_BIND_ATTRIBUTE = 'uid' 
     67    # The bind would be performed via: 
     68    # uid=username,ou=people,dc=example,dc=com 
     69 
     70    By inheriting this class you can change: 
     71    * How the dn to bind with is produced by overriding _pre_bind() 
     72    * What type of user object to use by overriding: _get_user_by_name(),  
     73      _create_user_object(), and get_user() 
     74    See mk_pre_auth.__doc__ for more information on overriding _pre_bind() 
     75    """  
     76    import ldap 
     77 
     78    def authenticate(self, username=None, password=None): 
     79        import ldap 
     80        if not username and password is not None: # we need a user/pass 
     81            return None 
     82 
     83        if hasattr(settings, 'LDAP_OPTIONS'): 
     84            for k in settings.LDAP_OPTIONS: 
     85                ldap.set_option(k, settings.LDAP_OPTIONS[k]) 
     86 
     87        l = ldap.initialize(settings.LDAP_SERVER_URI) 
     88 
     89        bind_string = self._pre_bind(l, username) 
     90 
     91        try: 
     92            l.bind_s(bind_string, password) 
     93        except ldap.INVALID_CREDENTIALS: # Failed user/pass 
     94            l.unbind_s() 
     95            return None 
     96 
     97        try: 
     98            user = self._get_user_by_name(username) 
     99        except User.DoesNotExist: 
     100            user = None 
     101 
     102        if user is not None: 
     103            if settings.LDAP_UPDATE_FIELDS: 
     104                self._update_user(l, user) 
     105        else: 
     106            user = self._get_ldap_user(l, username) 
     107 
     108        l.unbind_s() 
     109        return user 
     110 
     111# Functions provided to override to customize to your LDAP configuration. 
     112    def _pre_bind(self, l, username): 
     113        """ 
     114        Function that returns the dn to bind against ldap with. 
     115        called as: self._pre_bind(ldapobject, username) 
     116        """ 
     117        return "%s=%s,%s" % (settings.LDAP_BIND_ATTRIBUTE, username, 
     118                settings.LDAP_BINDDN) 
     119     
     120    def _get_user_by_name(self, username): 
     121        """ 
     122        Returns an object of contrib.auth.models.User that has a matching 
     123        username. 
     124        called as: self._get_user_by_name(username) 
     125        """ 
     126        return User.objects.get(username=username) 
     127 
     128    def _create_user_object(self, username, password): 
     129        """ 
     130        Creates and returns an object of contrib.auth.models.User. 
     131        called as: self._create_user_object(username, password) 
     132        """ 
     133        return User(username=username, password=password) 
     134 
     135    # Required for an authentication backend 
     136    def get_user(self, user_id): 
     137        try: 
     138            return User.objects.get(pk=user_id) 
     139        except: 
     140            return None 
     141# End of functions to override 
     142 
     143    def _get_ldap_user(self, l, username): 
     144        """ 
     145        Helper method, makes a user object and call update_user to populate 
     146        """ 
     147 
     148        # Generate a random password string. 
     149        from random import choice 
     150        import string 
     151        password = ''.join([choice(string.printable) for i in range(10)]) 
     152 
     153        user = self._create_user_object(username, password) 
     154        self._update_user(l, user) 
     155        return user 
     156 
     157    def _update_user(self, l, user): 
     158        """ 
     159        Helper method, populates a user object with various attributes from 
     160        LDAP. 
     161        """ 
     162 
     163        username = user.username 
     164        filter = settings.LDAP_SEARCH_FILTER % username 
     165 
     166        # Get results of search and make sure something was found. 
     167        # At this point this shouldn't fail. 
     168        hold = l.search_s(settings.LDAP_SEARCHDN, settings.LDAP_SCOPE, 
     169                filter) 
     170        if len(hold) < 1: 
     171            raise AssertionError('No results found with: %s' % (filter)) 
     172 
     173        attrs = hold[0][1] 
     174 
     175        if (hasattr(settings, 'LDAP_FIRST_NAME') 
     176                and hasattr(settings, 'LDAP_LAST_NAME')): 
     177            if (settings.LDAP_FIRST_NAME in attrs 
     178                    and settings.LDAP_LAST_NAME in attrs): 
     179                user.first_name = attrs[settings.LDAP_FIRST_NAME][0] 
     180                user.last_name = attrs[settings.LDAP_LAST_NAME][0] 
     181            else: 
     182                raise NameError('Missing needed fields %s or %s in LDAP' 
     183                        % (settings.LDAP_FIRST_NAME, settings.LDAP_LAST_NAME)) 
     184        elif hasattr(settings, 'LDAP_FULL_NAME'): 
     185                if settings.LDAP_FULL_NAME in attrs: 
     186                    tmp = attrs[settings.FULL_NAME_FIELD][0] 
     187                    user.first_name = tmp.split(' ')[0] 
     188                    user.last_name = ' '.join(tmp.split(' ')[1:]) 
     189                else: 
     190                    raise NameError('Required field %s missing in LDAP' 
     191                            % (settings.LDAP_FULL_NAME)) 
     192        else: 
     193            raise NameError('Name fields not defined in settings.py') 
     194 
     195        if hasattr(settings, 'LDAP_EMAIL') and settings.LDAP_EMAIL in attrs: 
     196            user.email = attrs[settings.EMAIL_FIELD][0] 
     197        elif hasattr(settings, 'LDAP_DEFAULT_EMAIL_SUFFIX'): 
     198            user.email = username + settings.LDAP_DEFAULT_EMAIL_SUFFIX 
     199 
     200        if (hasattr(settings, 'LDAP_GID') 
     201                and settings.LDAP_GID in attrs  
     202                and hasattr(settings, 'LDAP_SU_GIDS') 
     203                and attrs[settings.LDAP_GID][0] in settings.LDAP_SU_GIDS): 
     204            user.is_superuser = True 
     205            user.is_staff = True 
     206        elif (hasattr(settings, 'LDAP_GID') 
     207                and settings.LDAP_GID in attrs  
     208                and hasattr(settings, 'LDAP_STAFF_GIDS') 
     209                and attrs[settings.LDAP_GID][0] in settings.LDAP_STAFF_GIDS): 
     210            user.is_superuser = False 
     211            user.is_staff = True 
     212        else: 
     213            user.is_superuser = False 
     214            user.is_staff = False 
     215 
     216        user.save() 
     217 
     218    def mk_pre_auth_bind(auth_user, auth_pass, 
     219            search_base=settings.LDAP_SEARCHDN, 
     220            search_scope=settings.LDAP_SCOPE, 
     221            search_filter=settings.LDAP_SEARCH_FILTER): 
     222        """ 
     223        Returns a function, to be used to override pre_bind(). 
     224        auth_user -- string, ldap formatted user to bind with 
     225        auth_pass -- string, password for auth_user 
     226        search_base -- string, base for the ldap search 
     227        search_filter -- string, ldap search filter to use, %'d with username 
     228        search_scope -- ldap.SCOPE_* value, search scope 
     229 
     230        Used in a child class: 
     231        class MyLDAPBackend(LDAPBackend): 
     232            pre_bind = LDAPBackend.mk_pre_auth_bind('dn=Me,dc=example,dc=com', 
     233                    'pass') 
     234        which defaults to: 
     235        class MyLDAPBackend(LDAPBackend): 
     236            pre_bind = LDAPBackend.mk_pre_auth_bind('dn=Me,dc=example,dc=com', 
     237                    'pass', LDAP_SEARCHDN, LDAP_SCOPE, LDAP_SEARCH_FILTER) 
     238        or you can customize it: 
     239        class MyLDAPBackend(LDAPBackend): 
     240            pre_bind = LDAPBackend.mk_pre_auth_bind('dn=Me,dc=example,dc=com', 
     241                    'pass', 'ou=people,dc=example,dc=com'', 
     242                    ldap.SCOPE_SUBTREE, '(&(objectclass=person) (cn=%s))') 
     243        """ 
     244        def pre_auth_bind(l, user):  
     245            try:  
     246                l.simple_bind_s(auth_user, auth_pass) 
     247            except ldap.LDAPError: 
     248                return None 
     249 
     250            filter = search_filter % user 
     251 
     252            result = l.search_s(search_base, search_scope, filter, 
     253                    attrsonly=1) 
     254 
     255            if len(result) != 1: 
     256                return None 
     257 
     258            return result[0][0] 
     259        return pre_auth_bind