2 This API is adapted for OpenLDAP. The file contains all LDAP classes and methods
4 - Load the LDAP connection configuration file (login, address..) with LdapConfig
5 - Connect to LDAP with ldap_co
6 - Create a unique LDAP login and password for a user based on his email or last
7 name and first name with LoginPassword.
8 - Manage entries in LDAP using SFA records with LDAPapi (Search, Add, Delete,
13 from passlib.hash import ldap_salted_sha1 as lssha
15 from sfa.util.xrn import get_authority
16 from sfa.util.sfalogging import logger
17 from sfa.util.config import Config
20 import ldap.modlist as modlist
27 Ldap configuration class loads the configuration file and sets the
28 ldap IP address, password, people dn, web dn, group dn. All these settings
29 were defined in a separate file ldap_config.py to avoid sharing them in
30 the SFA git as it contains sensible information.
33 def __init__(self, config_file='/etc/sfa/ldap_config.py'):
34 """Loads configuration from file /etc/sfa/ldap_config.py and set the
35 parameters for connection to LDAP.
40 execfile(config_file, self.__dict__)
42 self.config_file = config_file
43 # path to configuration data
44 self.config_path = os.path.dirname(config_file)
46 raise IOError, "Could not find or load the configuration file: %s" \
51 """ Set admin login and server configuration variables."""
54 """Fetch LdapConfig attributes (Ldap server connection parameters and
55 defines port , version and subtree scope.
58 #Iotlab PROD LDAP parameters
60 ldap_config = LdapConfig()
61 self.config = ldap_config
62 self.ldapHost = ldap_config.LDAP_IP_ADDRESS
63 self.ldapPeopleDN = ldap_config.LDAP_PEOPLE_DN
64 self.ldapGroupDN = ldap_config.LDAP_GROUP_DN
65 self.ldapAdminDN = ldap_config.LDAP_WEB_DN
66 self.ldapAdminPassword = ldap_config.LDAP_WEB_PASSWORD
67 self.ldapPort = ldap.PORT
68 self.ldapVersion = ldap.VERSION3
69 self.ldapSearchScope = ldap.SCOPE_SUBTREE
71 def connect(self, bind=True):
72 """Enables connection to the LDAP server.
74 :param bind: Set the bind parameter to True if a bind is needed
75 (for add/modify/delete operations). Set to False otherwise.
77 :returns: dictionary with status of the connection. True if Successful,
78 False if not and in this case the error
79 message( {'bool', 'message'} ).
84 self.ldapserv = ldap.open(self.ldapHost)
85 except ldap.LDAPError, error:
86 return {'bool': False, 'message': error}
88 # Bind with authentification
98 :returns: dictionary with the bind status. True if Successful,
99 False if not and in this case the error message({'bool','message'})
104 # Opens a connection after a call to ldap.open in connect:
105 self.ldapserv = ldap.initialize("ldap://" + self.ldapHost)
107 # Bind/authenticate with a user with apropriate
108 #rights to add objects
109 self.ldapserv.simple_bind_s(self.ldapAdminDN,
110 self.ldapAdminPassword)
111 return {'bool': True}
113 except ldap.LDAPError, error:
114 return {'bool': False, 'message': error}
119 """Close the LDAP connection.
121 Can throw an exception if the unbinding fails.
123 :returns: dictionary with the bind status if the unbinding failed and
124 in this case the dict contains an error message. The dictionary keys
125 are : ({'bool','message'})
130 self.ldapserv.unbind_s()
131 except ldap.LDAPError, error:
132 return {'bool': False, 'message': error}
135 class LoginPassword():
138 Class to handle login and password generation, using custom login generation
145 Sets password and login maximum length, and defines the characters that
146 can be found in a random generated password.
149 self.login_max_length = 8
150 self.length_password = 8
151 self.chars_password = ['!', '$', '(',')', '*', '+', ',', '-', '.',
152 '0', '1', '2', '3', '4', '5', '6', '7', '8',
153 '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
154 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
155 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
156 '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
157 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
158 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
162 def clean_user_names(record):
165 Removes special characters such as '-', '_' , '[', ']' and ' ' from the
166 first name and last name.
168 :param record: user's record
170 :returns: lower_first_name and lower_last_name if they were found
171 in the user's record. Return None, none otherwise.
172 :rtype: string, string or None, None.
175 if 'first_name' in record and 'last_name' in record:
176 #Remove all special characters from first_name/last name
177 lower_first_name = record['first_name'].replace('-', '')\
178 .replace('_', '').replace('[', '')\
179 .replace(']', '').replace(' ', '')\
181 lower_last_name = record['last_name'].replace('-', '')\
182 .replace('_', '').replace('[', '')\
183 .replace(']', '').replace(' ', '')\
185 return lower_first_name, lower_last_name
190 def extract_name_from_email(record):
193 When there is no valid first name and last name in the record,
194 the email is used to generate the login. Here, we assume the email
195 is firstname.lastname@something.smthg. The first name and last names
196 are extracted from the email, special charcaters are removed and
197 they are changed into lower case.
199 :param record: user's data
201 :returns: the first name and last name taken from the user's email.
202 lower_first_name, lower_last_name.
203 :rtype: string, string
207 email = record['email']
208 email = email.split('@')[0].lower()
209 lower_first_name = None
210 lower_last_name = None
211 #Assume there is first name and last name in email
212 #if there is a separator
213 separator_list = ['.', '_', '-']
214 for sep in separator_list:
216 mail = email.split(sep)
217 lower_first_name = mail[0]
218 lower_last_name = mail[1]
221 #Otherwise just take the part before the @ as the
222 #lower_first_name and lower_last_name
223 if lower_first_name is None:
224 lower_first_name = email
225 lower_last_name = email
227 return lower_first_name, lower_last_name
229 def get_user_firstname_lastname(self, record):
232 Get the user first name and last name from the information we have in
235 :param record: user's information
237 :returns: the user's first name and last name.
239 .. seealso:: clean_user_names
240 .. seealso:: extract_name_from_email
243 lower_first_name, lower_last_name = self.clean_user_names(record)
245 #No first name and last name check email
246 if lower_first_name is None and lower_last_name is None:
248 lower_first_name, lower_last_name = \
249 self.extract_name_from_email(record)
251 return lower_first_name, lower_last_name
253 # XXX JORDAN: This function writes an error in the log but returns normally :))
254 def choose_sets_chars_for_login(self, lower_first_name, lower_last_name):
257 Algorithm to select sets of characters from the first name and last
258 name, depending on the lenght of the last name and the maximum login
259 length which in our case is set to 8 characters.
261 :param lower_first_name: user's first name in lower case.
262 :param lower_last_name: usr's last name in lower case.
263 :returns: user's login
267 length_last_name = len(lower_last_name)
268 self.login_max_length = 8
270 #Try generating a unique login based on first name and last name
272 if length_last_name >= self.login_max_length:
273 login = lower_last_name[0:self.login_max_length]
275 logger.debug("login : %s index : %s" % (login, index))
276 elif length_last_name >= 4:
277 login = lower_last_name
279 logger.debug("login : %s index : %s" % (login, index))
280 elif length_last_name == 3:
281 login = lower_first_name[0:1] + lower_last_name
283 logger.debug("login : %s index : %s" % (login, index))
284 elif length_last_name == 2:
285 if len(lower_first_name) >= 2:
286 login = lower_first_name[0:2] + lower_last_name
288 logger.debug("login : %s index : %s" % (login, index))
290 logger.error("LoginException : \
291 Generation login error with \
292 minimum four characters")
295 logger.error("LDAP LdapGenerateUniqueLogin failed : \
296 impossible to generate unique login for %s %s"
297 % (lower_first_name, lower_last_name))
298 logger.debug("JORDAN choose_sets_chars_for_login %d %s" % (index, login))
301 def generate_password(self):
304 Generate a password upon adding a new user in LDAP Directory
305 (8 characters length). The generated password is composed of characters
306 from the chars_password list.
308 :returns: the randomly generated password
314 length = len(self.chars_password)
315 for index in range(self.length_password):
316 char_index = random.randint(0, length - 1)
317 password += self.chars_password[char_index]
322 def encrypt_password(password):
325 Use passlib library to make a RFC2307 LDAP encrypted password salt size
326 is 8, use sha-1 algorithm.
328 :param password: password not encrypted.
329 :type password: string
330 :returns: Returns encrypted password.
334 #Keep consistency with Java Iotlab's LDAP API
335 #RFC2307SSHAPasswordEncryptor so set the salt size to 8 bytes
336 return lssha.encrypt(password, salt_size=8)
340 """Defines functions to insert and search entries in the LDAP.
342 .. note:: class supposes the unix schema is used
346 logger.setLevelDebug()
351 self.login_pwd = LoginPassword()
352 self.authname = config.SFA_REGISTRY_ROOT_AUTH
353 self.conn = ldap_co()
354 self.ldapUserQuotaNFS = self.conn.config.LDAP_USER_QUOTA_NFS
355 self.ldapUserUidNumberMin = self.conn.config.LDAP_USER_UID_NUMBER_MIN
356 self.ldapUserGidNumber = self.conn.config.LDAP_USER_GID_NUMBER
357 self.ldapUserHomePath = self.conn.config.LDAP_USER_HOME_PATH
358 self.baseDN = self.conn.ldapPeopleDN
359 self.ldapShell = '/bin/bash'
362 def LdapGenerateUniqueLogin(self, record):
365 Generate login for adding a new user in LDAP Directory
366 (four characters minimum length). Get proper last name and
367 first name so that the user's login can be generated.
369 :param record: Record must contain first_name and last_name.
371 :returns: the generated login for the user described with record if the
372 login generation is successful, None if it fails.
373 :rtype: string or None
376 #For compatibility with other ldap func
377 if 'mail' in record and 'email' not in record:
378 record['email'] = record['mail']
380 lower_first_name, lower_last_name = \
381 self.login_pwd.get_user_firstname_lastname(record)
383 index, login = self.login_pwd.choose_sets_chars_for_login(
384 lower_first_name, lower_last_name)
386 login_filter = '(uid=' + login + ')'
389 #Check if login already in use
391 while (len(self.LdapSearch(login_filter, get_attrs)) is not 0):
395 logger.error("LoginException : Generation login error \
396 with minimum four characters")
401 lower_first_name[0:index] + \
403 self.login_pwd.login_max_length
405 logger.debug("JORDAN trying login: %r" % login)
406 login_filter = '(uid=' + login + ')'
408 print "lower_first_name - lower_last_name too short"
410 logger.debug("LDAP.API \t LdapGenerateUniqueLogin login %s"
414 except ldap.LDAPError, error:
415 logger.log_exc("LDAP LdapGenerateUniqueLogin Error %s" % (error))
418 def find_max_uidNumber(self):
419 """Find the LDAP max uidNumber (POSIX uid attribute).
421 Used when adding a new user in LDAP Directory
423 :returns: max uidNumber + 1
427 #First, get all the users in the LDAP
428 get_attrs = "(uidNumber=*)"
429 login_filter = ['uidNumber']
431 result_data = self.LdapSearch(get_attrs, login_filter)
432 #It there is no user in LDAP yet, First LDAP user
433 if result_data == []:
434 max_uidnumber = self.ldapUserUidNumberMin
435 #Otherwise, get the highest uidNumber
437 uidNumberList = [int(r[1]['uidNumber'][0])for r in result_data]
438 logger.debug("LDAPapi.py \tfind_max_uidNumber \
439 uidNumberList %s " % (uidNumberList))
440 max_uidnumber = max(uidNumberList) + 1
442 return str(max_uidnumber)
445 def get_ssh_pkey(self, record):
446 """TODO ; Get ssh public key from sfa record
447 To be filled by N. Turro ? or using GID pl way?
453 #TODO Handle OR filtering in the ldap query when
454 #dealing with a list of records instead of doing a for loop in GetPersons
455 def make_ldap_filters_from_record(record=None):
456 """Helper function to make LDAP filter requests out of SFA records.
458 :param record: user's sfa record. Should contain first_name,last_name,
459 email or mail, and if the record is enabled or not. If the dict
460 record does not have all of these, must at least contain the user's
463 :returns: LDAP request
467 logger.debug("JORDAN make_ldap_filters_from_record: %r" % record)
471 if 'first_name' in record and 'last_name' in record:
472 if record['first_name'] != record['last_name']:
473 req_ldapdict['cn'] = str(record['first_name'])+" "\
474 + str(record['last_name'])
476 req_ldapdict['uid'] = record['uid']
477 if 'email' in record:
478 req_ldapdict['mail'] = record['email']
480 req_ldapdict['mail'] = record['mail']
481 if 'enabled' in record:
482 if record['enabled'] is True:
483 req_ldapdict['shadowExpire'] = '-1'
485 req_ldapdict['shadowExpire'] = '0'
487 #Hrn should not be part of the filter because the hrn
488 #presented by a certificate of a SFA user not imported in
489 #Iotlab does not include the iotlab login in it
490 #Plus, the SFA user may already have an account with iotlab
491 #using another login.
493 logger.debug("\r\n \t LDAP.PY make_ldap_filters_from_record \
494 record %s req_ldapdict %s"
495 % (record, req_ldapdict))
497 for k in req_ldapdict:
498 req_ldap += '(' + str(k) + '=' + str(req_ldapdict[k]) + ')'
499 if len(req_ldapdict.keys()) >1 :
500 req_ldap = req_ldap[:0]+"(&"+req_ldap[0:]
502 req_ldap = req_ldap[:(size-1)] + ')' + req_ldap[(size-1):]
508 def make_ldap_attributes_from_record(self, record):
511 When adding a new user to Iotlab's LDAP, creates an attributes
512 dictionnary from the SFA record understandable by LDAP. Generates the
513 user's LDAP login.User is automatically validated (account enabled)
514 and described as a SFA USER FROM OUTSIDE IOTLAB.
516 :param record: must contain the following keys and values:
517 first_name, last_name, mail, pkey (ssh key).
519 :returns: dictionary of attributes using LDAP data structure model.
523 logger.debug("JORDAN make_ldap_attributes_from_record: %r" % record)
526 attrs['objectClass'] = ["top", "person", "inetOrgPerson",
527 "organizationalPerson", "posixAccount",
528 "shadowAccount", "systemQuotas",
531 attrs['uid'] = self.LdapGenerateUniqueLogin(record)
533 attrs['givenName'] = str(record['first_name']).lower().capitalize()
534 attrs['sn'] = str(record['last_name']).lower().capitalize()
535 attrs['cn'] = attrs['givenName'] + ' ' + attrs['sn']
536 attrs['gecos'] = attrs['givenName'] + ' ' + attrs['sn']
539 attrs['givenName'] = attrs['uid']
540 attrs['sn'] = attrs['uid']
541 attrs['cn'] = attrs['uid']
542 attrs['gecos'] = attrs['uid']
544 attrs['quota'] = self.ldapUserQuotaNFS
545 attrs['homeDirectory'] = self.ldapUserHomePath + attrs['uid']
546 attrs['loginShell'] = self.ldapShell
547 attrs['gidNumber'] = self.ldapUserGidNumber
548 attrs['uidNumber'] = self.find_max_uidNumber()
549 attrs['mail'] = record['mail'].lower()
551 attrs['sshPublicKey'] = record['pkey']
553 attrs['sshPublicKey'] = self.get_ssh_pkey(record)
556 #Password is automatically generated because SFA user don't go
557 #through the Iotlab website used to register new users,
558 #There is no place in SFA where users can enter such information
560 #If the user wants to set his own password , he must go to the Iotlab
562 password = self.login_pwd.generate_password()
563 attrs['userPassword'] = self.login_pwd.encrypt_password(password)
565 #Account automatically validated (no mail request to admins)
566 #Set to 0 to disable the account, -1 to enable it,
567 attrs['shadowExpire'] = '-1'
569 #Motivation field in Iotlab
570 attrs['description'] = 'SFA USER FROM OUTSIDE SENSLAB'
572 attrs['ou'] = 'SFA' #Optional: organizational unit
573 #No info about those here:
574 attrs['l'] = 'To be defined'#Optional: Locality.
575 attrs['st'] = 'To be defined' #Optional: state or province (country).
580 def LdapAddUser(self, record) :
581 """Add SFA user to LDAP if it is not in LDAP yet.
583 :param record: dictionnary with the user's data.
584 :returns: a dictionary with the status (Fail= False, Success= True)
585 and the uid of the newly added user if successful, or the error
586 message it is not. Dict has keys bool and message in case of
587 failure, and bool uid in case of success.
590 .. seealso:: make_ldap_filters_from_record
593 filter_by = self.make_ldap_filters_from_record({'email' : record['email']})
594 user = self.LdapSearch(filter_by)
596 logger.debug("LDAPapi.py user ldap exist \t%s" % user)
597 # user = [('uid=saint,ou=People,dc=senslab,dc=info', {'uid': ['saint'], 'givenName': ['Fred'], ...})]
598 return {'bool': True, 'uid': user[0][1]['uid'][0]}
600 user_ldap_attrs = self.make_ldap_attributes_from_record(record)
601 result = self.conn.connect()
603 logger.debug("LDAPapi.py user ldap doesn't exist \t%s" % user_ldap_attrs)
604 # The dn of our new entry/object
605 dn = 'uid=' + user_ldap_attrs['uid'] + "," + self.baseDN
607 ldif = modlist.addModlist(user_ldap_attrs)
608 self.conn.ldapserv.add_s(dn, ldif)
610 return {'bool': True, 'uid': user_ldap_attrs['uid']}
611 except ldap.LDAPError, error:
612 logger.log_exc("LDAP Add Error %s" % error)
613 return {'bool': False, 'message': error}
618 def LdapDelete(self, person_dn):
619 """Deletes a person in LDAP. Uses the dn of the user.
621 :param person_dn: user's ldap dn.
622 :type person_dn: string
623 :returns: dictionary with bool True if successful, bool False
624 and the error if not.
629 result = self.conn.connect()
632 self.conn.ldapserv.delete_s(person_dn)
634 return {'bool': True}
636 except ldap.LDAPError, error:
637 logger.log_exc("LDAP Delete Error %s" % error)
638 return {'bool': False, 'message': error}
640 def LdapDeleteUser(self, record_filter):
641 """Deletes a SFA person in LDAP, based on the user's hrn.
643 :param record_filter: Filter to find the user to be deleted. Must
644 contain at least the user's email.
645 :type record_filter: dict
646 :returns: dict with bool True if successful, bool False and error
650 .. seealso:: LdapFindUser docstring for more info on record filter.
651 .. seealso:: LdapDelete for user deletion
654 #Find uid of the person
655 person = self.LdapFindUser(record_filter, [])
656 logger.debug("LDAPapi.py \t LdapDeleteUser record %s person %s"
657 % (record_filter, person))
660 dn = 'uid=' + person['uid'] + "," + self.baseDN
662 return {'bool': False}
664 result = self.LdapDelete(dn)
667 def LdapModify(self, dn, old_attributes_dict, new_attributes_dict):
668 """ Modifies a LDAP entry, replaces user's old attributes with
671 :param dn: user's absolute name in the LDAP hierarchy.
672 :param old_attributes_dict: old user's attributes. Keys must match
673 the ones used in the LDAP model.
674 :param new_attributes_dict: new user's attributes. Keys must match
675 the ones used in the LDAP model.
677 :type old_attributes_dict: dict
678 :type new_attributes_dict: dict
679 :returns: dict bool True if Successful, bool False if not.
684 ldif = modlist.modifyModlist(old_attributes_dict, new_attributes_dict)
685 # Connect and bind/authenticate
686 result = self.conn.connect()
689 self.conn.ldapserv.modify_s(dn, ldif)
691 return {'bool': True}
692 except ldap.LDAPError, error:
693 logger.log_exc("LDAP LdapModify Error %s" % error)
694 return {'bool': False}
697 def LdapModifyUser(self, user_record, new_attributes_dict):
700 Gets the record from one user based on the user sfa recordand changes
701 the attributes according to the specified new_attributes. Do not use
702 this if we need to modify the uid. Use a ModRDN operation instead
703 ( modify relative DN ).
705 :param user_record: sfa user record.
706 :param new_attributes_dict: new user attributes, keys must be the
707 same as the LDAP model.
708 :type user_record: dict
709 :type new_attributes_dict: dict
710 :returns: bool True if successful, bool False if not.
713 .. seealso:: make_ldap_filters_from_record for info on what is mandatory
715 .. seealso:: make_ldap_attributes_from_record for the LDAP objectclass.
718 if user_record is None:
719 logger.error("LDAP \t LdapModifyUser Need user record ")
720 return {'bool': False}
722 #Get all the attributes of the user_uid_login
723 #person = self.LdapFindUser(record_filter,[])
724 req_ldap = self.make_ldap_filters_from_record(user_record)
725 person_list = self.LdapSearch(req_ldap, [])
726 logger.debug("LDAPapi.py \t LdapModifyUser person_list : %s"
729 if person_list and len(person_list) > 1:
730 logger.error("LDAP \t LdapModifyUser Too many users returned")
731 return {'bool': False}
732 if person_list is None:
733 logger.error("LDAP \t LdapModifyUser User %s doesn't exist "
735 return {'bool': False}
737 # The dn of our existing entry/object
738 #One result only from ldapSearch
739 person = person_list[0][1]
740 dn = 'uid=' + person['uid'][0] + "," + self.baseDN
742 if new_attributes_dict:
744 for k in new_attributes_dict:
749 logger.debug(" LDAPapi.py \t LdapModifyUser new_attributes %s"
750 % (new_attributes_dict))
751 result = self.LdapModify(dn, old, new_attributes_dict)
754 logger.error("LDAP \t LdapModifyUser No new attributes given. ")
755 return {'bool': False}
758 def LdapMarkUserAsDeleted(self, record):
761 Sets shadowExpire to 0, disabling the user in LDAP. Calls LdapModifyUser
762 to change the shadowExpire of the user.
764 :param record: the record of the user who has to be disabled.
765 Should contain first_name,last_name, email or mail, and if the
766 record is enabled or not. If the dict record does not have all of
767 these, must at least contain the user's email.
769 :returns: {bool: True} if successful or {bool: False} if not
772 .. seealso:: LdapModifyUser, make_ldap_attributes_from_record
777 new_attrs['shadowExpire'] = '0'
778 logger.debug(" LDAPapi.py \t LdapMarkUserAsDeleted ")
779 ret = self.LdapModifyUser(record, new_attrs)
782 def LdapResetPassword(self, record):
783 """Resets password for the user whose record is the parameter and
784 changes the corresponding entry in the LDAP.
786 :param record: user's sfa record whose Ldap password must be reset.
787 Should contain first_name,last_name,
788 email or mail, and if the record is enabled or not. If the dict
789 record does not have all of these, must at least contain the user's
792 :returns: return value of LdapModifyUser. True if successful, False
795 .. seealso:: LdapModifyUser, make_ldap_attributes_from_record
798 password = self.login_pwd.generate_password()
800 attrs['userPassword'] = self.login_pwd.encrypt_password(password)
801 logger.debug("LDAP LdapResetPassword encrypt_password %s"
802 % (attrs['userPassword']))
803 result = self.LdapModifyUser(record, attrs)
807 def LdapSearch(self, req_ldap=None, expected_fields=None):
809 Used to search directly in LDAP, by using ldap filters and return
810 fields. When req_ldap is None, returns all the entries in the LDAP.
812 :param req_ldap: ldap style request, with appropriate filters,
814 :param expected_fields: Fields in the user ldap entry that has to be
815 returned. If None is provided, will return 'mail', 'givenName',
816 'sn', 'uid', 'sshPublicKey', 'shadowExpire'.
817 :type req_ldap: string
818 :type expected_fields: list
820 .. seealso:: make_ldap_filters_from_record for req_ldap format.
823 logger.debug("JORDAN LdapSearch, req_ldap=%r, expected_fields=%r" % (req_ldap, expected_fields))
824 result = self.conn.connect(bind=False)
827 return_fields_list = []
828 if expected_fields is None:
829 return_fields_list = ['mail', 'givenName', 'sn', 'uid',
830 'sshPublicKey', 'shadowExpire']
832 return_fields_list = expected_fields
833 #No specifc request specified, get the whole LDAP
837 logger.debug("LDAP.PY \t LdapSearch req_ldap %s \
838 return_fields_list %s" \
839 %(req_ldap, return_fields_list))
842 msg_id = self.conn.ldapserv.search(
843 self.baseDN, ldap.SCOPE_SUBTREE,
844 req_ldap, return_fields_list)
845 #Get all the results matching the search from ldap in one
847 result_type, result_data = \
848 self.conn.ldapserv.result(msg_id, 1)
852 logger.debug("LDAP.PY \t LdapSearch result_data %s"
857 except ldap.LDAPError, error:
858 logger.log_exc("LDAP LdapSearch Error %s" % error)
862 logger.error("LDAP.PY \t Connection Failed")
865 def _process_ldap_info_for_all_users(self, result_data):
866 """Process the data of all enabled users in LDAP.
868 :param result_data: Contains information of all enabled users in LDAP
869 and is coming from LdapSearch.
870 :param result_data: list
872 .. seealso:: LdapSearch
876 logger.debug(" LDAP.py _process_ldap_info_for_all_users result_data %s "
878 for ldapentry in result_data:
879 logger.debug(" LDAP.py _process_ldap_info_for_all_users \
880 ldapentry name : %s " % (ldapentry[1]['uid'][0]))
881 tmpname = ldapentry[1]['uid'][0]
882 hrn = self.authname + "." + tmpname
884 tmpemail = ldapentry[1]['mail'][0]
885 if ldapentry[1]['mail'][0] == "unknown":
891 'pkey': ldapentry[1]['sshPublicKey'][0],
892 #'uid': ldapentry[1]['uid'][0],
895 #'email': ldapentry[1]['mail'][0],
896 'first_name': ldapentry[1]['givenName'][0],
897 'last_name': ldapentry[1]['sn'][0],
900 'authority': self.authname,
901 'peer_authority': '',
905 except KeyError, error:
906 logger.log_exc("LDAPapi.PY \t LdapFindUser EXCEPTION %s"
912 def _process_ldap_info_for_one_user(self, record, result_data):
915 Put the user's ldap data into shape. Only deals with one user
916 record and one user data from ldap.
918 :param record: user record
919 :param result_data: Raw ldap data coming from LdapSearch
920 :returns: user's data dict with 'type','pkey','uid', 'email',
921 'first_name' 'last_name''serial''authority''peer_authority'
924 :type result_data: list
928 #One entry only in the ldap data because we used a filter
929 #to find one user only
930 ldapentry = result_data[0][1]
931 logger.debug("LDAP.PY \t LdapFindUser ldapentry %s" % (ldapentry))
932 tmpname = ldapentry['uid'][0]
934 tmpemail = ldapentry['mail'][0]
935 if ldapentry['mail'][0] == "unknown":
939 peer_authority = None
940 # If the user is coming from External authority (e.g. OneLab)
941 # Then hrn is None, it should be filled in by the creation of Ldap User
942 # XXX LOIC !!! What if a user email is in 2 authorities?
943 if 'hrn' in record and record['hrn'] is not None:
945 parent_hrn = get_authority(hrn)
946 if parent_hrn != self.authname:
947 peer_authority = parent_hrn
948 #In case the user was not imported from Iotlab LDAP
949 #but from another federated site, has an account in
950 #iotlab but currently using his hrn from federated site
951 #then the login is different from the one found in its hrn
952 if tmpname != hrn.split('.')[1]:
960 'pkey': ldapentry['sshPublicKey'],
961 #'uid': ldapentry[1]['uid'][0],
964 #'email': ldapentry[1]['mail'][0],
965 'first_name': ldapentry['givenName'][0],
966 'last_name': ldapentry['sn'][0],
969 'authority': parent_hrn,
970 'peer_authority': peer_authority,
977 'pkey': ldapentry['sshPublicKey'],
978 #'uid': ldapentry[1]['uid'][0],
981 #'email': ldapentry[1]['mail'][0],
982 'first_name': ldapentry['givenName'][0],
983 'last_name': ldapentry['sn'][0],
986 'authority': parent_hrn,
987 'peer_authority': peer_authority,
993 def LdapFindUser(self, record=None, is_user_enabled=None,
994 expected_fields=None):
997 Search a SFA user with a hrn. User should be already registered
1000 :param record: sfa user's record. Should contain first_name,last_name,
1001 email or mail. If no record is provided, returns all the users found
1004 :param is_user_enabled: is the user's iotlab account already valid.
1005 :type is_user_enabled: Boolean.
1006 :returns: LDAP entries from ldap matching the filter provided. Returns
1007 a single entry if one filter has been given and a list of
1009 :rtype: dict or list
1012 logger.debug("JORDAN LdapFindUser record=%r, is_user_enabled=%r, expected_fields=%r" % (record, is_user_enabled, expected_fields))
1016 custom_record['enabled'] = is_user_enabled
1018 custom_record.update(record)
1020 req_ldap = self.make_ldap_filters_from_record(custom_record)
1021 return_fields_list = []
1022 if expected_fields is None:
1023 return_fields_list = ['mail', 'givenName', 'sn', 'uid',
1026 return_fields_list = expected_fields
1028 result_data = self.LdapSearch(req_ldap, return_fields_list)
1029 logger.debug("LDAP.PY \t LdapFindUser result_data %s" % (result_data))
1031 if len(result_data) == 0:
1033 #Asked for a specific user
1034 if record is not None:
1035 logger.debug("LOIC - record = %s" % record)
1036 results = self._process_ldap_info_for_one_user(record, result_data)
1039 #Asked for all users in ldap
1040 results = self._process_ldap_info_for_all_users(result_data)