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)
112 except ldap.LDAPError, error:
113 return {'bool': False, 'message': error}
115 return {'bool': True}
118 """Close the LDAP connection.
120 Can throw an exception if the unbinding fails.
122 :returns: dictionary with the bind status if the unbinding failed and
123 in this case the dict contains an error message. The dictionary keys
124 are : ({'bool','message'})
129 self.ldapserv.unbind_s()
130 except ldap.LDAPError, error:
131 return {'bool': False, 'message': error}
134 class LoginPassword():
137 Class to handle login and password generation, using custom login generation
144 Sets password and login maximum length, and defines the characters that
145 can be found in a random generated password.
148 self.login_max_length = 8
149 self.length_password = 8
150 self.chars_password = ['!', '$', '(',')', '*', '+', ',', '-', '.',
151 '0', '1', '2', '3', '4', '5', '6', '7', '8',
152 '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
153 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
154 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
155 '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
156 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
157 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
161 def clean_user_names(record):
164 Removes special characters such as '-', '_' , '[', ']' and ' ' from the
165 first name and last name.
167 :param record: user's record
169 :returns: lower_first_name and lower_last_name if they were found
170 in the user's record. Return None, none otherwise.
171 :rtype: string, string or None, None.
174 if 'first_name' in record and 'last_name' in record:
175 #Remove all special characters from first_name/last name
176 lower_first_name = record['first_name'].replace('-', '')\
177 .replace('_', '').replace('[', '')\
178 .replace(']', '').replace(' ', '')\
180 lower_last_name = record['last_name'].replace('-', '')\
181 .replace('_', '').replace('[', '')\
182 .replace(']', '').replace(' ', '')\
184 return lower_first_name, lower_last_name
189 def extract_name_from_email(record):
192 When there is no valid first name and last name in the record,
193 the email is used to generate the login. Here, we assume the email
194 is firstname.lastname@something.smthg. The first name and last names
195 are extracted from the email, special charcaters are removed and
196 they are changed into lower case.
198 :param record: user's data
200 :returns: the first name and last name taken from the user's email.
201 lower_first_name, lower_last_name.
202 :rtype: string, string
206 email = record['email']
207 email = email.split('@')[0].lower()
208 lower_first_name = None
209 lower_last_name = None
210 #Assume there is first name and last name in email
211 #if there is a separator
212 separator_list = ['.', '_', '-']
213 for sep in separator_list:
215 mail = email.split(sep)
216 lower_first_name = mail[0]
217 lower_last_name = mail[1]
220 #Otherwise just take the part before the @ as the
221 #lower_first_name and lower_last_name
222 if lower_first_name is None:
223 lower_first_name = email
224 lower_last_name = email
226 return lower_first_name, lower_last_name
228 def get_user_firstname_lastname(self, record):
231 Get the user first name and last name from the information we have in
234 :param record: user's information
236 :returns: the user's first name and last name.
238 .. seealso:: clean_user_names
239 .. seealso:: extract_name_from_email
242 lower_first_name, lower_last_name = self.clean_user_names(record)
244 #No first name and last name check email
245 if lower_first_name is None and lower_last_name is None:
247 lower_first_name, lower_last_name = \
248 self.extract_name_from_email(record)
250 return lower_first_name, lower_last_name
252 # XXX JORDAN: This function writes an error in the log but returns normally :))
253 def choose_sets_chars_for_login(self, lower_first_name, lower_last_name):
256 Algorithm to select sets of characters from the first name and last
257 name, depending on the lenght of the last name and the maximum login
258 length which in our case is set to 8 characters.
260 :param lower_first_name: user's first name in lower case.
261 :param lower_last_name: usr's last name in lower case.
262 :returns: user's login
266 length_last_name = len(lower_last_name)
267 self.login_max_length = 8
269 #Try generating a unique login based on first name and last name
271 if length_last_name >= self.login_max_length:
272 login = lower_last_name[0:self.login_max_length]
274 logger.debug("login : %s index : %s" % (login, index))
275 elif length_last_name >= 4:
276 login = lower_last_name
278 logger.debug("login : %s index : %s" % (login, index))
279 elif length_last_name == 3:
280 login = lower_first_name[0:1] + lower_last_name
282 logger.debug("login : %s index : %s" % (login, index))
283 elif length_last_name == 2:
284 if len(lower_first_name) >= 2:
285 login = lower_first_name[0:2] + lower_last_name
287 logger.debug("login : %s index : %s" % (login, index))
289 logger.error("LoginException : \
290 Generation login error with \
291 minimum four characters")
294 logger.error("LDAP LdapGenerateUniqueLogin failed : \
295 impossible to generate unique login for %s %s"
296 % (lower_first_name, lower_last_name))
297 logger.debug("JORDAN choose_sets_chars_for_login %d %s" % (index, login))
300 def generate_password(self):
303 Generate a password upon adding a new user in LDAP Directory
304 (8 characters length). The generated password is composed of characters
305 from the chars_password list.
307 :returns: the randomly generated password
313 length = len(self.chars_password)
314 for index in range(self.length_password):
315 char_index = random.randint(0, length - 1)
316 password += self.chars_password[char_index]
321 def encrypt_password(password):
324 Use passlib library to make a RFC2307 LDAP encrypted password salt size
325 is 8, use sha-1 algorithm.
327 :param password: password not encrypted.
328 :type password: string
329 :returns: Returns encrypted password.
333 #Keep consistency with Java Iotlab's LDAP API
334 #RFC2307SSHAPasswordEncryptor so set the salt size to 8 bytes
335 return lssha.encrypt(password, salt_size=8)
339 """Defines functions to insert and search entries in the LDAP.
341 .. note:: class supposes the unix schema is used
345 logger.setLevelDebug()
350 self.login_pwd = LoginPassword()
351 self.authname = config.SFA_REGISTRY_ROOT_AUTH
352 self.conn = ldap_co()
353 self.ldapUserQuotaNFS = self.conn.config.LDAP_USER_QUOTA_NFS
354 self.ldapUserUidNumberMin = self.conn.config.LDAP_USER_UID_NUMBER_MIN
355 self.ldapUserGidNumber = self.conn.config.LDAP_USER_GID_NUMBER
356 self.ldapUserHomePath = self.conn.config.LDAP_USER_HOME_PATH
357 self.baseDN = self.conn.ldapPeopleDN
358 self.ldapShell = '/bin/bash'
361 def LdapGenerateUniqueLogin(self, record):
364 Generate login for adding a new user in LDAP Directory
365 (four characters minimum length). Get proper last name and
366 first name so that the user's login can be generated.
368 :param record: Record must contain first_name and last_name.
370 :returns: the generated login for the user described with record if the
371 login generation is successful, None if it fails.
372 :rtype: string or None
375 #For compatibility with other ldap func
376 if 'mail' in record and 'email' not in record:
377 record['email'] = record['mail']
379 lower_first_name, lower_last_name = \
380 self.login_pwd.get_user_firstname_lastname(record)
382 index, login = self.login_pwd.choose_sets_chars_for_login(
383 lower_first_name, lower_last_name)
385 login_filter = '(uid=' + login + ')'
388 #Check if login already in use
390 while (len(self.LdapSearch(login_filter, get_attrs)) is not 0):
394 logger.error("LoginException : Generation login error \
395 with minimum four characters")
400 lower_first_name[0:index] + \
402 self.login_pwd.login_max_length
404 logger.debug("JORDAN trying login: %r" % login)
405 login_filter = '(uid=' + login + ')'
407 print "lower_first_name - lower_last_name too short"
409 logger.debug("LDAP.API \t LdapGenerateUniqueLogin login %s"
413 except ldap.LDAPError, error:
414 logger.log_exc("LDAP LdapGenerateUniqueLogin Error %s" % (error))
417 def find_max_uidNumber(self):
418 """Find the LDAP max uidNumber (POSIX uid attribute).
420 Used when adding a new user in LDAP Directory
422 :returns: max uidNumber + 1
426 #First, get all the users in the LDAP
427 get_attrs = "(uidNumber=*)"
428 login_filter = ['uidNumber']
430 result_data = self.LdapSearch(get_attrs, login_filter)
431 #It there is no user in LDAP yet, First LDAP user
432 if result_data == []:
433 max_uidnumber = self.ldapUserUidNumberMin
434 #Otherwise, get the highest uidNumber
436 uidNumberList = [int(r[1]['uidNumber'][0])for r in result_data]
437 logger.debug("LDAPapi.py \tfind_max_uidNumber \
438 uidNumberList %s " % (uidNumberList))
439 max_uidnumber = max(uidNumberList) + 1
441 return str(max_uidnumber)
444 def get_ssh_pkey(self, record):
445 """TODO ; Get ssh public key from sfa record
446 To be filled by N. Turro ? or using GID pl way?
452 #TODO Handle OR filtering in the ldap query when
453 #dealing with a list of records instead of doing a for loop in GetPersons
454 def make_ldap_filters_from_record(record=None):
455 """Helper function to make LDAP filter requests out of SFA records.
457 :param record: user's sfa record. Should contain first_name,last_name,
458 email or mail, and if the record is enabled or not. If the dict
459 record does not have all of these, must at least contain the user's
462 :returns: LDAP request
466 logger.debug("JORDAN make_ldap_filters_from_record: %r" % record)
470 if 'first_name' in record and 'last_name' in record:
471 if record['first_name'] != record['last_name']:
472 req_ldapdict['cn'] = str(record['first_name'])+" "\
473 + str(record['last_name'])
475 req_ldapdict['uid'] = record['uid']
476 if 'email' in record:
477 req_ldapdict['mail'] = record['email']
479 req_ldapdict['mail'] = record['mail']
480 if 'enabled' in record:
481 if record['enabled'] is True:
482 req_ldapdict['shadowExpire'] = '-1'
484 req_ldapdict['shadowExpire'] = '0'
486 #Hrn should not be part of the filter because the hrn
487 #presented by a certificate of a SFA user not imported in
488 #Iotlab does not include the iotlab login in it
489 #Plus, the SFA user may already have an account with iotlab
490 #using another login.
492 logger.debug("\r\n \t LDAP.PY make_ldap_filters_from_record \
493 record %s req_ldapdict %s"
494 % (record, req_ldapdict))
496 for k in req_ldapdict:
497 req_ldap += '(' + str(k) + '=' + str(req_ldapdict[k]) + ')'
498 if len(req_ldapdict.keys()) >1 :
499 req_ldap = req_ldap[:0]+"(&"+req_ldap[0:]
501 req_ldap = req_ldap[:(size-1)] + ')' + req_ldap[(size-1):]
507 def make_ldap_attributes_from_record(self, record):
510 When adding a new user to Iotlab's LDAP, creates an attributes
511 dictionnary from the SFA record understandable by LDAP. Generates the
512 user's LDAP login.User is automatically validated (account enabled)
513 and described as a SFA USER FROM OUTSIDE IOTLAB.
515 :param record: must contain the following keys and values:
516 first_name, last_name, mail, pkey (ssh key).
518 :returns: dictionary of attributes using LDAP data structure model.
522 logger.debug("JORDAN make_ldap_attributes_from_record: %r" % record)
525 attrs['objectClass'] = ["top", "person", "inetOrgPerson",
526 "organizationalPerson", "posixAccount",
527 "shadowAccount", "systemQuotas",
530 attrs['uid'] = self.LdapGenerateUniqueLogin(record)
532 attrs['givenName'] = str(record['first_name']).lower().capitalize()
533 attrs['sn'] = str(record['last_name']).lower().capitalize()
534 attrs['cn'] = attrs['givenName'] + ' ' + attrs['sn']
535 attrs['gecos'] = attrs['givenName'] + ' ' + attrs['sn']
538 attrs['givenName'] = attrs['uid']
539 attrs['sn'] = attrs['uid']
540 attrs['cn'] = attrs['uid']
541 attrs['gecos'] = attrs['uid']
543 attrs['quota'] = self.ldapUserQuotaNFS
544 attrs['homeDirectory'] = self.ldapUserHomePath + attrs['uid']
545 attrs['loginShell'] = self.ldapShell
546 attrs['gidNumber'] = self.ldapUserGidNumber
547 attrs['uidNumber'] = self.find_max_uidNumber()
548 attrs['mail'] = record['mail'].lower()
550 attrs['sshPublicKey'] = record['pkey']
552 attrs['sshPublicKey'] = self.get_ssh_pkey(record)
555 #Password is automatically generated because SFA user don't go
556 #through the Iotlab website used to register new users,
557 #There is no place in SFA where users can enter such information
559 #If the user wants to set his own password , he must go to the Iotlab
561 password = self.login_pwd.generate_password()
562 attrs['userPassword'] = self.login_pwd.encrypt_password(password)
564 #Account automatically validated (no mail request to admins)
565 #Set to 0 to disable the account, -1 to enable it,
566 attrs['shadowExpire'] = '-1'
568 #Motivation field in Iotlab
569 attrs['description'] = 'SFA USER FROM OUTSIDE SENSLAB'
571 attrs['ou'] = 'SFA' #Optional: organizational unit
572 #No info about those here:
573 attrs['l'] = 'To be defined'#Optional: Locality.
574 attrs['st'] = 'To be defined' #Optional: state or province (country).
579 def LdapAddUser(self, record) :
580 """Add SFA user to LDAP if it is not in LDAP yet.
582 :param record: dictionnary with the user's data.
583 :returns: a dictionary with the status (Fail= False, Success= True)
584 and the uid of the newly added user if successful, or the error
585 message it is not. Dict has keys bool and message in case of
586 failure, and bool uid in case of success.
589 .. seealso:: make_ldap_filters_from_record
592 logger.debug(" \r\n \t LDAP LdapAddUser \r\n\r\n ================\r\n ")
593 user_ldap_attrs = self.make_ldap_attributes_from_record(record)
594 logger.debug("JORDAN LdapAddUser (ctd) user_ldap_attrs=%r" % user_ldap_attrs)
596 #Check if user already in LDAP wih email, first name and last name
597 filter_by = self.make_ldap_filters_from_record(user_ldap_attrs)
598 logger.debug("JORDAN LdapAddUser (ctd) filter_by = %r" % filter_by)
599 user_exist = self.LdapSearch(filter_by)
600 logger.debug("JORDAN LdapAddUser (ctd) user_exist = %r" % user_exist)
602 logger.warning(" \r\n \t LDAP LdapAddUser user %s %s \
603 already exists" % (user_ldap_attrs['sn'],
604 user_ldap_attrs['mail']))
605 return {'bool': False}
608 result = self.conn.connect()
609 logger.debug("JORDAN LdapAddUser (ctd) result = %r" % result)
613 # A dict to help build the "body" of the object
614 logger.debug(" \r\n \t LDAP LdapAddUser attrs %s "
617 # The dn of our new entry/object
618 dn = 'uid=' + user_ldap_attrs['uid'] + "," + self.baseDN
621 ldif = modlist.addModlist(user_ldap_attrs)
622 logger.debug("LDAPapi.py add attrs %s \r\n ldif %s"
623 % (user_ldap_attrs, ldif))
624 self.conn.ldapserv.add_s(dn, ldif)
626 logger.info("Adding user %s login %s in LDAP"
627 % (user_ldap_attrs['cn'], user_ldap_attrs['uid']))
628 except ldap.LDAPError, error:
629 logger.log_exc("LDAP Add Error %s" % error)
630 return {'bool': False, 'message': error}
633 return {'bool': True, 'uid': user_ldap_attrs['uid']}
637 def LdapDelete(self, person_dn):
638 """Deletes a person in LDAP. Uses the dn of the user.
640 :param person_dn: user's ldap dn.
641 :type person_dn: string
642 :returns: dictionary with bool True if successful, bool False
643 and the error if not.
648 result = self.conn.connect()
651 self.conn.ldapserv.delete_s(person_dn)
653 return {'bool': True}
655 except ldap.LDAPError, error:
656 logger.log_exc("LDAP Delete Error %s" % error)
657 return {'bool': False, 'message': error}
659 def LdapDeleteUser(self, record_filter):
660 """Deletes a SFA person in LDAP, based on the user's hrn.
662 :param record_filter: Filter to find the user to be deleted. Must
663 contain at least the user's email.
664 :type record_filter: dict
665 :returns: dict with bool True if successful, bool False and error
669 .. seealso:: LdapFindUser docstring for more info on record filter.
670 .. seealso:: LdapDelete for user deletion
673 #Find uid of the person
674 person = self.LdapFindUser(record_filter, [])
675 logger.debug("LDAPapi.py \t LdapDeleteUser record %s person %s"
676 % (record_filter, person))
679 dn = 'uid=' + person['uid'] + "," + self.baseDN
681 return {'bool': False}
683 result = self.LdapDelete(dn)
686 def LdapModify(self, dn, old_attributes_dict, new_attributes_dict):
687 """ Modifies a LDAP entry, replaces user's old attributes with
690 :param dn: user's absolute name in the LDAP hierarchy.
691 :param old_attributes_dict: old user's attributes. Keys must match
692 the ones used in the LDAP model.
693 :param new_attributes_dict: new user's attributes. Keys must match
694 the ones used in the LDAP model.
696 :type old_attributes_dict: dict
697 :type new_attributes_dict: dict
698 :returns: dict bool True if Successful, bool False if not.
703 ldif = modlist.modifyModlist(old_attributes_dict, new_attributes_dict)
704 # Connect and bind/authenticate
705 result = self.conn.connect()
708 self.conn.ldapserv.modify_s(dn, ldif)
710 return {'bool': True}
711 except ldap.LDAPError, error:
712 logger.log_exc("LDAP LdapModify Error %s" % error)
713 return {'bool': False}
716 def LdapModifyUser(self, user_record, new_attributes_dict):
719 Gets the record from one user based on the user sfa recordand changes
720 the attributes according to the specified new_attributes. Do not use
721 this if we need to modify the uid. Use a ModRDN operation instead
722 ( modify relative DN ).
724 :param user_record: sfa user record.
725 :param new_attributes_dict: new user attributes, keys must be the
726 same as the LDAP model.
727 :type user_record: dict
728 :type new_attributes_dict: dict
729 :returns: bool True if successful, bool False if not.
732 .. seealso:: make_ldap_filters_from_record for info on what is mandatory
734 .. seealso:: make_ldap_attributes_from_record for the LDAP objectclass.
737 if user_record is None:
738 logger.error("LDAP \t LdapModifyUser Need user record ")
739 return {'bool': False}
741 #Get all the attributes of the user_uid_login
742 #person = self.LdapFindUser(record_filter,[])
743 req_ldap = self.make_ldap_filters_from_record(user_record)
744 person_list = self.LdapSearch(req_ldap, [])
745 logger.debug("LDAPapi.py \t LdapModifyUser person_list : %s"
748 if person_list and len(person_list) > 1:
749 logger.error("LDAP \t LdapModifyUser Too many users returned")
750 return {'bool': False}
751 if person_list is None:
752 logger.error("LDAP \t LdapModifyUser User %s doesn't exist "
754 return {'bool': False}
756 # The dn of our existing entry/object
757 #One result only from ldapSearch
758 person = person_list[0][1]
759 dn = 'uid=' + person['uid'][0] + "," + self.baseDN
761 if new_attributes_dict:
763 for k in new_attributes_dict:
768 logger.debug(" LDAPapi.py \t LdapModifyUser new_attributes %s"
769 % (new_attributes_dict))
770 result = self.LdapModify(dn, old, new_attributes_dict)
773 logger.error("LDAP \t LdapModifyUser No new attributes given. ")
774 return {'bool': False}
777 def LdapMarkUserAsDeleted(self, record):
780 Sets shadowExpire to 0, disabling the user in LDAP. Calls LdapModifyUser
781 to change the shadowExpire of the user.
783 :param record: the record of the user who has to be disabled.
784 Should contain first_name,last_name, email or mail, and if the
785 record is enabled or not. If the dict record does not have all of
786 these, must at least contain the user's email.
788 :returns: {bool: True} if successful or {bool: False} if not
791 .. seealso:: LdapModifyUser, make_ldap_attributes_from_record
796 new_attrs['shadowExpire'] = '0'
797 logger.debug(" LDAPapi.py \t LdapMarkUserAsDeleted ")
798 ret = self.LdapModifyUser(record, new_attrs)
801 def LdapResetPassword(self, record):
802 """Resets password for the user whose record is the parameter and
803 changes the corresponding entry in the LDAP.
805 :param record: user's sfa record whose Ldap password must be reset.
806 Should contain first_name,last_name,
807 email or mail, and if the record is enabled or not. If the dict
808 record does not have all of these, must at least contain the user's
811 :returns: return value of LdapModifyUser. True if successful, False
814 .. seealso:: LdapModifyUser, make_ldap_attributes_from_record
817 password = self.login_pwd.generate_password()
819 attrs['userPassword'] = self.login_pwd.encrypt_password(password)
820 logger.debug("LDAP LdapResetPassword encrypt_password %s"
821 % (attrs['userPassword']))
822 result = self.LdapModifyUser(record, attrs)
826 def LdapSearch(self, req_ldap=None, expected_fields=None):
828 Used to search directly in LDAP, by using ldap filters and return
829 fields. When req_ldap is None, returns all the entries in the LDAP.
831 :param req_ldap: ldap style request, with appropriate filters,
833 :param expected_fields: Fields in the user ldap entry that has to be
834 returned. If None is provided, will return 'mail', 'givenName',
835 'sn', 'uid', 'sshPublicKey', 'shadowExpire'.
836 :type req_ldap: string
837 :type expected_fields: list
839 .. seealso:: make_ldap_filters_from_record for req_ldap format.
842 logger.debug("JORDAN LdapSearch, req_ldap=%r, expected_fields=%r" % (req_ldap, expected_fields))
843 result = self.conn.connect(bind=False)
846 return_fields_list = []
847 if expected_fields is None:
848 return_fields_list = ['mail', 'givenName', 'sn', 'uid',
849 'sshPublicKey', 'shadowExpire']
851 return_fields_list = expected_fields
852 #No specifc request specified, get the whole LDAP
856 logger.debug("LDAP.PY \t LdapSearch req_ldap %s \
857 return_fields_list %s" \
858 %(req_ldap, return_fields_list))
861 msg_id = self.conn.ldapserv.search(
862 self.baseDN, ldap.SCOPE_SUBTREE,
863 req_ldap, return_fields_list)
864 #Get all the results matching the search from ldap in one
866 result_type, result_data = \
867 self.conn.ldapserv.result(msg_id, 1)
871 logger.debug("LDAP.PY \t LdapSearch result_data %s"
876 except ldap.LDAPError, error:
877 logger.log_exc("LDAP LdapSearch Error %s" % error)
881 logger.error("LDAP.PY \t Connection Failed")
884 def _process_ldap_info_for_all_users(self, result_data):
885 """Process the data of all enabled users in LDAP.
887 :param result_data: Contains information of all enabled users in LDAP
888 and is coming from LdapSearch.
889 :param result_data: list
891 .. seealso:: LdapSearch
895 logger.debug(" LDAP.py _process_ldap_info_for_all_users result_data %s "
897 for ldapentry in result_data:
898 logger.debug(" LDAP.py _process_ldap_info_for_all_users \
899 ldapentry name : %s " % (ldapentry[1]['uid'][0]))
900 tmpname = ldapentry[1]['uid'][0]
901 hrn = self.authname + "." + tmpname
903 tmpemail = ldapentry[1]['mail'][0]
904 if ldapentry[1]['mail'][0] == "unknown":
910 'pkey': ldapentry[1]['sshPublicKey'][0],
911 #'uid': ldapentry[1]['uid'][0],
914 #'email': ldapentry[1]['mail'][0],
915 'first_name': ldapentry[1]['givenName'][0],
916 'last_name': ldapentry[1]['sn'][0],
919 'authority': self.authname,
920 'peer_authority': '',
924 except KeyError, error:
925 logger.log_exc("LDAPapi.PY \t LdapFindUser EXCEPTION %s"
931 def _process_ldap_info_for_one_user(self, record, result_data):
934 Put the user's ldap data into shape. Only deals with one user
935 record and one user data from ldap.
937 :param record: user record
938 :param result_data: Raw ldap data coming from LdapSearch
939 :returns: user's data dict with 'type','pkey','uid', 'email',
940 'first_name' 'last_name''serial''authority''peer_authority'
943 :type result_data: list
947 #One entry only in the ldap data because we used a filter
948 #to find one user only
949 ldapentry = result_data[0][1]
950 logger.debug("LDAP.PY \t LdapFindUser ldapentry %s" % (ldapentry))
951 tmpname = ldapentry['uid'][0]
953 tmpemail = ldapentry['mail'][0]
954 if ldapentry['mail'][0] == "unknown":
958 peer_authority = None
959 # If the user is coming from External authority (e.g. OneLab)
960 # Then hrn is None, it should be filled in by the creation of Ldap User
961 # XXX LOIC !!! What if a user email is in 2 authorities?
962 if 'hrn' in record and record['hrn'] is not None:
964 parent_hrn = get_authority(hrn)
965 if parent_hrn != self.authname:
966 peer_authority = parent_hrn
967 #In case the user was not imported from Iotlab LDAP
968 #but from another federated site, has an account in
969 #iotlab but currently using his hrn from federated site
970 #then the login is different from the one found in its hrn
971 if tmpname != hrn.split('.')[1]:
979 'pkey': ldapentry['sshPublicKey'],
980 #'uid': ldapentry[1]['uid'][0],
983 #'email': ldapentry[1]['mail'][0],
984 'first_name': ldapentry['givenName'][0],
985 'last_name': ldapentry['sn'][0],
988 'authority': parent_hrn,
989 'peer_authority': peer_authority,
996 'pkey': ldapentry['sshPublicKey'],
997 #'uid': ldapentry[1]['uid'][0],
1000 #'email': ldapentry[1]['mail'][0],
1001 'first_name': ldapentry['givenName'][0],
1002 'last_name': ldapentry['sn'][0],
1005 'authority': parent_hrn,
1006 'peer_authority': peer_authority,
1012 def LdapFindUser(self, record=None, is_user_enabled=None,
1013 expected_fields=None):
1016 Search a SFA user with a hrn. User should be already registered
1019 :param record: sfa user's record. Should contain first_name,last_name,
1020 email or mail. If no record is provided, returns all the users found
1023 :param is_user_enabled: is the user's iotlab account already valid.
1024 :type is_user_enabled: Boolean.
1025 :returns: LDAP entries from ldap matching the filter provided. Returns
1026 a single entry if one filter has been given and a list of
1028 :rtype: dict or list
1031 logger.debug("JORDAN LdapFindUser record=%r, is_user_enabled=%r, expected_fields=%r" % (record, is_user_enabled, expected_fields))
1035 custom_record['enabled'] = is_user_enabled
1037 custom_record.update(record)
1039 req_ldap = self.make_ldap_filters_from_record(custom_record)
1040 return_fields_list = []
1041 if expected_fields is None:
1042 return_fields_list = ['mail', 'givenName', 'sn', 'uid',
1045 return_fields_list = expected_fields
1047 result_data = self.LdapSearch(req_ldap, return_fields_list)
1048 logger.debug("LDAP.PY \t LdapFindUser result_data %s" % (result_data))
1050 if len(result_data) == 0:
1052 #Asked for a specific user
1053 if record is not None:
1054 logger.debug("LOIC - record = %s" % record)
1055 results = self._process_ldap_info_for_one_user(record, result_data)
1058 #Asked for all users in ldap
1059 results = self._process_ldap_info_for_all_users(result_data)