This is a snapshot of Indico's old Trac site. Any information contained herein is most probably outdated. Access our new GitHub site here.

Ticket #1038: LDAPAuthentication.py.patch

File LDAPAuthentication.py.patch, 14.6 KB (added by makub, 3 years ago)

implemented alternative group membership queries for Active Directory and SLAPD

  • MaKaC/authentication/

    old new  
    3535o: Example Inc. 
    3636postalAddress: Example Inc., Some City, Some Country 
    3737 
    38 and groups listing their members by DNs, like: 
     38and groups in the OpenLDAP/SLAPD format listing their members by DNs, like: 
    3939 
    4040dn: cn=somegroup,ou=groups,dc=example,dc=com 
    4141objectClass: groupOfNames 
     
    4545member: uid=bob,ou=people,dc=example,dc=com 
    4646description: Just a group of people ... 
    4747 
    48 Adjust it to your needs if your LDAP structure is different. 
     48or groups in ActiveDirectory format marked by 'memberof' attribute. 
     49 
     50Adjust it to your needs if your LDAP structure is different, 
     51preferably by changing the extractUserDataFromLdapData() method. 
    4952 
    5053See indico.conf for information about customization options. 
    5154""" 
     
    5457try: 
    5558    import ldap 
    5659    import ldap.filter 
     60    import re 
    5761except: 
    5862    pass 
    5963 
     
    6771RETRIEVED_FIELDS = ['uid', 'cn', 'mail', 'o', 'ou', 'company', 'givenName', 
    6872                    'sn', 'postalAddress', 'userPrincipalName'] 
    6973 
     74def extractUserDataFromLdapData(ret): 
     75    """extracts user data from a LDAP record as a dictionary, edit to modify for your needs""" 
     76    udata= {} 
     77    udata["login"] = ret['uid'] 
     78    udata["email"] = ret['mail'] 
     79    udata["name"]= ret.get('givenName', '') 
     80    udata["surName"]= ret.get('sn', '') 
     81    udata["organisation"] = ret.get('o','')  
     82    udata['address'] = fromLDAPmultiline(ret['postalAddress']) if 'postalAddress' in ret else '' 
     83    Logger.get('auth.ldap').debug("extractUserDataFromLdapData(): %s " % udata) 
     84    return udata 
    7085 
    7186class LDAPAuthenticator(Authenthicator): 
    7287    idxName = "LDAPIdentities" 
     
    88103            return None 
    89104 
    90105 
     106 
    91107class LDAPIdentity(PIdentity): 
    92108 
    93109    def __str__(self): 
     
    98114        """ 
    99115        id is MaKaC.user.LoginInfo instance, self.user is Avatar 
    100116        """ 
    101  
    102         Logger.get('auth.ldap').info("authenticate(%s)" % id.getLogin()) 
     117        log = Logger.get('auth.ldap') 
     118        log.info("authenticate(%s)" % id.getLogin()) 
    103119        data = LDAPChecker().check(id.getLogin(), id.getPassword()) 
    104120        if data: 
    105121            if self.getLogin() == id.getLogin(): 
    106122                # modify Avatar with the up-to-date info from LDAP 
    107123                av = self.user 
    108  
    109124                av.clearAuthenticatorPersonalData() 
    110  
    111                 if 'postalAddress' in data: 
    112                     postalAddress = fromLDAPmultiline(data['postalAddress']) 
    113                     if av.getAddress() != postalAddress: 
    114                         av.setAddress(postalAddress) 
    115  
    116                 if 'sn' in data: 
    117                     surname = data['sn'] 
    118                     av.setAuthenticatorPersonalData('surName', surname) 
    119                     if surname and av.getSurName() != surname and av.isFieldSynced('surName'): 
    120                         av.setSurName(surname, reindex=True) 
    121  
    122                 if 'givenName' in data: 
    123                     firstName = data['givenName'] 
     125                udata = extractUserDataFromLdapData(data) 
     126                if 'name' in udata: 
     127                    firstName = udata['name'] 
    124128                    av.setAuthenticatorPersonalData('firstName', firstName) 
    125129                    if firstName and av.getName() != firstName and av.isFieldSynced('firstName'): 
    126130                        av.setName(firstName, reindex=True) 
    127  
    128                 if 'o' in data: 
    129                     org = data.get('o', '') 
    130                 else: 
    131                     org = data.get('company', '') 
    132  
    133                 av.setAuthenticatorPersonalData('affiliation', org) 
    134                 if org.strip() != '' and org != av.getOrganisation() and av.isFieldSynced('affiliation'): 
    135                     av.setOrganisation(org, reindex=True) 
    136  
    137                 mail = data.get('mail', '') 
    138  
    139                 if mail.strip() != '' and mail != av.getEmail(): 
    140                     av.setEmail(mail, reindex=True) 
    141  
     131                        log.info('updated name for user '+id.getLogin()+' to '+firstName) 
     132                if 'surName' in udata: 
     133                    surname = udata['surName'] 
     134                    av.setAuthenticatorPersonalData('surName', surname) 
     135                    if surname and av.getSurName() != surname and av.isFieldSynced('surName'): 
     136                        av.setSurName(surname, reindex=True) 
     137                        log.info('updated surName for user '+id.getLogin()+' to '+surname) 
     138                if 'organisation' in udata: 
     139                    org = udata['organisation'] 
     140                    av.setAuthenticatorPersonalData('affiliation', org) 
     141                    if org.strip() != '' and org != av.getOrganisation() and av.isFieldSynced('affiliation'): 
     142                        av.setOrganisation(org, reindex=True) 
     143                        log.info('updated organisation for user '+id.getLogin()+' to '+org) 
     144                if 'email' in udata: 
     145                    mail = udata['email'] 
     146                    if mail.strip() != '' and mail != av.getEmail(): 
     147                        av.setEmail(mail, reindex=True) 
     148                        log.info('updated email for user '+id.getLogin()+' to '+mail) 
     149                if 'address' in udata: 
     150                    address = udata['address'] 
     151                    if address != av.getAddress(): 
     152                        av.setFieldSynced('address',True) 
     153                        av.setAddress(address) 
     154                        log.info('updated address for user '+id.getLogin()+' to '+address) 
    142155                return self.user 
    143156            else: 
    144157                return None 
     
    147160    def getAuthenticatorTag(self): 
    148161        return LDAPAuthenticator.getId() 
    149162 
    150  
    151163def objectAttributes(dn, result_data, attributeNames): 
    152164    """ 
    153165    adds selected attributes 
     
    193205        self.ldapGroupsFilter, self.ldapGroupsDN = \ 
    194206                               ldapConfig.get('groupDNQuery') 
    195207        self.ldapAccessCredentials = ldapConfig.get('accessCredentials') 
    196         self.ldapMembershipQuery = ldapConfig.get('membershipQuery') 
    197208        self.ldapUseTLS = ldapConfig.get('useTLS') 
     209        self.groupStyle = ldapConfig.get('groupStyle') 
    198210 
    199211    def login(self): 
    200212        try: 
     
    269281 
    270282        for dn, data in res: 
    271283            if dn: 
     284                Logger.get('auth.ldap').debug('lookupUser(%s) successful'%uid) 
    272285                return objectAttributes(dn, data, RETRIEVED_FIELDS) 
    273286        return None 
    274287 
     
    303316            gfilter = self.ldapGroupsFilter.format(star + name + star) 
    304317        else: 
    305318            return [] 
     319        Logger.get('auth.ldap').debug('findGroups(%s) '%name) 
    306320        res = self.l.search_s(self.ldapGroupsDN, ldap.SCOPE_SUBTREE, gfilter) 
    307321        groupDicts = [] 
    308322        for dn, data in res: 
     
    313327 
    314328    def userInGroup(self, login, name): 
    315329        """ 
    316         Finds uids of users referenced by the member attribute 
    317         of the group LDAP object 
     330         Retursn whether a  user is in a group. Depends on groupStyle (SLAPD/ActiveDirectory) 
    318331        """ 
    319         query = self.ldapMembershipQuery.format(self._findDNOfGroup(name)) 
    320         res = self.l.search_s(self._findDNOfUser(login), ldap.SCOPE_BASE, query) 
    321  
     332        Logger.get('auth.ldap').debug('userInGroup(%s,%s) '%(login,name)) 
     333        # In ActiveDirectory users have multivalued attribute 'memberof' with list of groups 
     334        # In SLAPD groups have multivalues attribute 'member' with list of users 
     335        if self.groupStyle=='ActiveDirectory': 
     336            query = 'memberof={0}'.format(self._findDNOfGroup(name)) 
     337            res = self.l.search_s(self._findDNOfUser(login), ldap.SCOPE_BASE, query) 
     338        elif self.groupStyle=='SLAPD': 
     339            query = 'member={0}'.format(self._findDNOfUser(login)) 
     340            res = self.l.search_s(self._findDNOfGroup(name), ldap.SCOPE_BASE, query) 
     341        else: 
     342            raise Exception("Unknown LDAP group style, choices are: SLAPD or ActiveDirectory") 
    322343        return res != [] 
    323344 
     345    def findGroupMemberUids(self,name): 
     346        """ 
     347         Finds uids of users in a group. Depends on groupStyle (SLAPD/ActiveDirectory) 
     348        """ 
     349        Logger.get('auth.ldap').debug('findGroupMemberUids(%s) '%name) 
     350        # In ActiveDirectory users have multivalued attribute 'memberof' with list of groups 
     351        # In SLAPD groups have multivalues attribute 'member' with list of users 
     352        if self.groupStyle=='ActiveDirectory': 
     353            #!not tested, I have not ActiveDirectory instance to try test it 
     354            #search for users with attribute memberof=groupdn 
     355            memberUids = [] 
     356            query = 'memberof={0}'.format(self._findDNOfGroup(name)) 
     357            res = self.l.search_s(self.ldapPeopleDN, ldap.SCOPE_SUBTREE,query) 
     358            for dn, data in res: 
     359                if dn: 
     360                    memberUids.append( data['uid'] ) 
     361            return memberUids 
     362        elif self.groupStyle=='SLAPD': 
     363            #read member attibute values from the group object 
     364            members = None 
     365            res = self.l.search_s(self._findDNOfGroup(name), ldap.SCOPE_BASE) 
     366            for dn, data in res: 
     367                if dn: 
     368                    members = data['member'] 
     369            if not members: 
     370                return [] 
     371            memberUids = [] 
     372            for memberDN in members: 
     373                m = re.search('uid=([^,]*),',memberDN) 
     374                if m: 
     375                    uid = m.group(1) 
     376                    memberUids.append( uid ) 
     377            Logger.get('auth.ldap').debug('findGroupMemberUids(%s) returns %s'%(name,memberUids)) 
     378            return memberUids 
     379        else: 
     380            raise Exception("Unknown LDAP group style, choices are: SLAPD or ActiveDirectory") 
     381 
    324382 
    325383class LDAPChecker(object): 
    326384    def check(self, userName, password): 
     385        if not password or not password.strip(): 
     386            Logger.get('auth.ldap').info("Username: %s - empty password" % userName) 
     387            return None 
    327388        try: 
    328389            ret = {} 
    329390            ldapc = LDAPConnector() 
    330391            ldapc.openAsUser(userName, password) 
    331392            ret = ldapc.lookupUser(userName) 
    332393            ldapc.close() 
    333             Logger.get('auth.ldap').debug("Username: %s checked: %s" % \ 
    334                                           (userName, ret)) 
     394            Logger.get('auth.ldap').debug("Username: %s checked: %s" % (userName, ret)) 
     395            if not ret : 
     396                return None 
     397            #LDAP search is case-insensitive, we want case-sensitive match 
     398            if ret.get('uid')!=userName : 
     399                Logger.get('auth.ldap').info('user %s invalid case %s' % (userName,ret.get('uid'))) 
     400                return None 
    335401            return ret 
    336402        except ldap.INVALID_CREDENTIALS: 
    337             Logger.get('auth.ldap').exception( 
    338                 "Username: %s - invalid credentials" % userName) 
     403            Logger.get('auth.ldap').info("Username: %s - invalid credentials" % userName) 
    339404            return None 
    340405 
    341406 
     
    353418class LDAPUserCreator(object): 
    354419 
    355420    def create(self, li): 
    356         Logger.get('auth.ldap').info("create '%s'" % li.getLogin()) 
     421        Logger.get('auth.ldap').debug("create '%s'" % li.getLogin()) 
    357422        # first, check if authentication is OK 
    358423        data = LDAPChecker().check(li.getLogin(), li.getPassword()) 
    359424        if not data: 
     
    367432            # User doesn't exist, create it 
    368433            try: 
    369434                av = user.Avatar() 
    370                 name = data.get('cn') 
    371                 av.setName(name.split()[0]) 
    372                 av.setSurName(name.split()[-1]) 
    373                 av.setOrganisation(data.get('o', "")) 
    374                 av.setEmail(data['mail']) 
    375                 if 'postalAddress' in data: 
    376                     av.setAddress(fromLDAPmultiline(data.get('postalAddress'))) 
    377                 #av.setTelephone(data.get('telephonenumber',"")) 
     435                udata = extractUserDataFromLdapData(data) 
     436                av.setName(udata['name']) 
     437                av.setSurName(udata['surName']) 
     438                av.setOrganisation(udata['organisation']) 
     439                av.setEmail(udata['email']) 
     440                av.setAddress(udata['address']) 
    378441                ah.add(av) 
    379442                av.activateAccount() 
     443                Logger.get('auth.ldap').info("created '%s'" % li.getLogin()) 
    380444            except KeyError: 
    381445                raise MaKaCError("LDAP account does not contain the mandatory" 
    382446                                 "data to create an Indico account.") 
    383447        else: 
    384448            # user founded 
     449            Logger.get('auth.ldap').info("found user '%s'" % li.getLogin()) 
    385450            av = userList[0] 
    386451        #now create the nice identity for the user 
    387452        na = LDAPAuthenticator() 
     
    390455        return av 
    391456 
    392457 
    393 # for MaKaC.externUsers 
     458 
    394459def dictToAv(ret): 
     460    """converts user data obtained from LDAP to the structure expected by Avatar""" 
    395461    av = {} 
    396     av["email"] = [ret['mail']] 
    397     av["name"] = ret.get('givenName', '') 
    398     av["surName"] = ret.get('sn', '') 
    399  
    400     if 'o' in ret: 
    401         av["organisation"] = [ret.get('o', '')] 
    402     else: 
    403         av["organisation"] = [ret.get('company', '')] 
    404  
    405     if 'postalAddress' in ret: 
    406         av['address'] = [fromLDAPmultiline(ret['postalAddress'])] 
    407  
    408     av["login"] = ret.get('uid') if 'uid' in ret else ret['userPrincipalName'] 
    409     av["id"] = 'LDAP:' + av["login"] 
     462    udata=extractUserDataFromLdapData(ret) 
     463    av["login"] = udata["login"] 
     464    av["email"] = [udata["email"]] 
     465    av["name"]= udata["name"] 
     466    av["surName"]= udata["surName"] 
     467    av["organisation"] = [udata["organisation"]] 
     468    av["address"] = [udata["address"]] 
     469    av["id"] = 'LDAP:'+udata["login"] 
    410470    av["status"] = "NotCreated" 
    411471    return av 
    412472 
    413473 
    414 def is_empty(dict, key): 
    415     if key not in dict: 
    416         return False 
    417     if dict[key]: 
    418         return True 
    419     else: 
    420         return False 
    421  
    422  
    423474class LDAPUser(object): 
    424475 
    425476    _operations = { 
     
    457508        av["id"] = id 
    458509        av["identity"] = LDAPIdentity 
    459510        av["authenticator"] = LDAPAuthenticator() 
     511        Logger.get('auth.ldap').debug('LDAPUser.getById(%s) return %s '%(id,av)) 
    460512        return av 
    461513 
    462514 
    463515def ldapFindGroups(name, exact): 
     516    """used in user.py""" 
    464517    ldapc = LDAPConnector() 
    465518    ldapc.open() 
    466519    ldapc.login() 
     
    470523 
    471524 
    472525def ldapUserInGroup(user, name): 
     526    """used in user.py""" 
    473527    ldapc = LDAPConnector() 
    474528    ldapc.open() 
    475529    ldapc.login() 
    476530    ret = ldapc.userInGroup(user, name) 
    477531    ldapc.close() 
    478532    return ret 
     533 
     534def ldapFindGroupMemberUids(name): 
     535    """used in user.py""" 
     536    ldapc = LDAPConnector() 
     537    ldapc.open() 
     538    ldapc.login() 
     539    ret = ldapc.findGroupMemberUids(name) 
     540    ldapc.close() 
     541    return ret 
     542