fda3515e84be116d7e9bd2d6bca3d42d136cebd9
[sfa.git] / sfa / iotlab / LDAPapi.py
1 import random
2 from passlib.hash import ldap_salted_sha1 as lssha
3 from sfa.util.xrn import get_authority
4 import ldap
5 from sfa.util.config import Config
6
7
8 import ldap.modlist as modlist
9 from sfa.util.sfalogging import logger
10 import os.path
11
12 #API for OpenLDAP
13
14
15 class LdapConfig():
16     def __init__(self, config_file =  '/etc/sfa/ldap_config.py'):
17
18         try:
19             execfile(config_file, self.__dict__)
20
21             self.config_file = config_file
22             # path to configuration data
23             self.config_path = os.path.dirname(config_file)
24         except IOError:
25             raise IOError, "Could not find or load the configuration file: %s" \
26                             % config_file
27
28
29 class ldap_co:
30     """ Set admin login and server configuration variables."""
31
32     def __init__(self):
33         #Iotlab PROD LDAP parameters
34         self.ldapserv = None
35         ldap_config = LdapConfig()
36         self.config = ldap_config
37         self.ldapHost = ldap_config.LDAP_IP_ADDRESS
38         self.ldapPeopleDN = ldap_config.LDAP_PEOPLE_DN
39         self.ldapGroupDN = ldap_config.LDAP_GROUP_DN
40         self.ldapAdminDN = ldap_config.LDAP_WEB_DN
41         self.ldapAdminPassword = ldap_config.LDAP_WEB_PASSWORD
42
43
44         self.ldapPort = ldap.PORT
45         self.ldapVersion  = ldap.VERSION3
46         self.ldapSearchScope = ldap.SCOPE_SUBTREE
47
48
49     def connect(self, bind = True):
50         """
51         Enables connection to the LDAP server.
52         :param bind : Set the bind parameter to True if a bind is needed
53         (for add/modify/delete operations).
54         Set to False otherwise.
55         :type bind : boolean
56         :return: dictionary with status of the connection. True if Successful,
57         False if not and in this case the error message( {'bool', 'message'} )
58         :rtype:dict
59         """
60         try:
61             self.ldapserv = ldap.open(self.ldapHost)
62         except ldap.LDAPError, error:
63             return {'bool' : False, 'message' : error }
64
65         # Bind with authentification
66         if(bind):
67             return self.bind()
68
69         else:
70             return {'bool': True}
71
72     def bind(self):
73         """ Binding method.
74         :return: dictionary with the bind status. True if Successful,
75         False if not and in this case the error message( {'bool', 'message'} )
76         :rtype: dict
77
78         """
79         try:
80             # Opens a connection after a call to ldap.open in connect:
81             self.ldapserv = ldap.initialize("ldap://" + self.ldapHost)
82
83             # Bind/authenticate with a user with apropriate
84             #rights to add objects
85             self.ldapserv.simple_bind_s(self.ldapAdminDN, \
86                                     self.ldapAdminPassword)
87
88         except ldap.LDAPError, error:
89             return {'bool' : False, 'message' : error }
90
91         return {'bool': True}
92
93     def close(self):
94         """ Close the LDAP connection.
95         Can throw an exception if the unbinding fails.
96         """
97         try:
98             self.ldapserv.unbind_s()
99         except ldap.LDAPError, error:
100             return {'bool' : False, 'message' : error }
101
102 class LoginPassword():
103     """
104     Class to handle login and password generation, using custom login generation
105     algorithm.
106     """
107     def __init__(self):
108         """
109         Sets password  and login maximum length, and defines the characters
110         that can be found in a random generated password.
111         """
112         self.login_max_length  = 8
113         self.length_password = 8
114         self.chars_password = [ '!', '$', '(',')', '*', '+', ',', '-', '.', \
115                                 '0', '1', '2', '3', '4', '5', '6', '7', '8', \
116                                 '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', \
117                                 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', \
118                                 'R', 'S', 'T',  'U', 'V', 'W', 'X', 'Y', 'Z', \
119                                 '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', \
120                                 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p' ,'q', \
121                                 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', \
122                                 '\'']
123
124
125
126
127     @staticmethod
128     def clean_user_names(record):
129         """
130         Removes special characters such as
131         '-', '_' , '[', ']' and ' ' from the first name and last name.
132         :param record: user's record
133         :type record:dict
134         :return: lower_first_name and lower_last_name if they were found
135         in the user's record. Return None, none otherwise.
136         :rtype: string, string or None, None.
137         """
138         if 'first_name' in record and 'last_name' in record:
139             #Remove all special characters from first_name/last name
140             lower_first_name = record['first_name'].replace('-','')\
141                                             .replace('_','').replace('[','')\
142                                             .replace(']','').replace(' ','')\
143                                             .lower()
144             lower_last_name = record['last_name'].replace('-','')\
145                                             .replace('_','').replace('[','')\
146                                             .replace(']','').replace(' ','')\
147                                             .lower()
148             return lower_first_name, lower_last_name
149         else:
150             return None, None
151
152     @staticmethod
153     def extract_name_from_email(record):
154         """
155         When there is no valid first name and last name in the record,
156         the email is used to generate the login. Here, we assume the email
157         is firstname.lastname@something.smthg. The first name and last names
158         are extracted from the email, special charcaters are removed and
159         they are changed into lower case.
160         :param record: user's data
161         :type record:dict
162         :return: the first name and last name taken from the user's email.
163         lower_first_name, lower_last_name.
164         :rtype: string, string
165         """
166
167         email = record['email']
168         email = email.split('@')[0].lower()
169         lower_first_name = None
170         lower_last_name = None
171         #Assume there is first name and last name in email
172         #if there is a  separator
173         separator_list = ['.', '_', '-']
174         for sep in separator_list:
175             if sep in email:
176                 mail = email.split(sep)
177                 lower_first_name = mail[0]
178                 lower_last_name = mail[1]
179                 break
180
181         #Otherwise just take the part before the @ as the
182         #lower_first_name  and lower_last_name
183         if lower_first_name is None:
184             lower_first_name = email
185             lower_last_name = email
186
187         return lower_first_name, lower_last_name
188
189     def get_user_firstname_lastname(self, record):
190         """Get the user first name and last name from the information
191         we have in the record.
192         :param record: user's information
193         :type record: dict
194         :return: the user's first name and last name.
195         ..seealso: clean_user_names
196         ..seealso: extract_name_from_email
197         """
198         lower_first_name, lower_last_name = self.clean_user_names(record)
199
200         #No first name and last name  check  email
201         if lower_first_name is None and lower_last_name is None:
202
203             lower_first_name, lower_last_name = \
204                             self.extract_name_from_email(record)
205
206         return lower_first_name, lower_last_name
207
208
209     def choose_sets_chars_for_login(self, lower_first_name, lower_last_name):
210         """
211         Algorithm to select sets of characters from the first name and
212         last name, depending on the lenght of the last name and the
213         maximum login length which in our case is set to 8 charachetrs.
214         :param lower_first_name: user's first name in lower case.
215         :param lower_last_name: usr's last name in lower case.
216         :return: user's login
217         :rtype:string
218         """
219         length_last_name = len(lower_last_name)
220         self.login_max_length = 8
221
222         #Try generating a unique login based on first name and last name
223
224         if length_last_name >= self.login_max_length :
225             login = lower_last_name[0:self.login_max_length]
226             index = 0
227             logger.debug("login : %s index : %s" %(login, index))
228         elif length_last_name >= 4 :
229             login = lower_last_name
230             index = 0
231             logger.debug("login : %s index : %s" %(login, index))
232         elif length_last_name == 3 :
233             login = lower_first_name[0:1] + lower_last_name
234             index = 1
235             logger.debug("login : %s index : %s" %(login, index))
236         elif length_last_name == 2:
237             if len ( lower_first_name) >=2:
238                 login = lower_first_name[0:2] + lower_last_name
239                 index = 2
240                 logger.debug("login : %s index : %s" %(login, index))
241             else:
242                 logger.error("LoginException : \
243                             Generation login error with \
244                             minimum four characters")
245
246         else :
247             logger.error("LDAP LdapGenerateUniqueLogin failed : \
248                             impossible to generate unique login for %s %s" \
249                             %(lower_first_name,lower_last_name))
250         return index, login
251
252
253
254     def generate_password(self):
255
256         """Generate a password upon  adding a new user in LDAP Directory
257         (8 characters length). The generated password is composed  of characters
258         from the charsPassword list
259         :return: the randomly generated password
260         :rtype: string
261
262         """
263         password = str()
264
265         length = len(self.chars_password)
266         for index in range(self.length_password):
267             char_index = random.randint(0, length-1)
268             password += self.chars_password[char_index]
269
270         return password
271
272     @staticmethod
273     def encrypt_password(password):
274         """ Use passlib library to make a RFC2307 LDAP encrypted password
275         salt size = 8, use sha-1 algorithm.
276         :param password:  password not encrypted.
277         :type password: string
278         :return: Returns encrypted password.
279         :rtype:string
280         """
281         #Keep consistency with Java Iotlab's LDAP API
282         #RFC2307SSHAPasswordEncryptor so set the salt size to 8 bytes
283         return lssha.encrypt(password, salt_size = 8)
284
285
286
287 class LDAPapi :
288     def __init__(self):
289         logger.setLevelDebug()
290
291         #SFA related config
292
293         config = Config()
294         self.login_pwd = LoginPassword()
295         self.authname = config.SFA_REGISTRY_ROOT_AUTH
296
297         self.conn =  ldap_co()
298         self.ldapUserQuotaNFS = self.conn.config.LDAP_USER_QUOTA_NFS
299         self.ldapUserUidNumberMin = self.conn.config.LDAP_USER_UID_NUMBER_MIN
300         self.ldapUserGidNumber = self.conn.config.LDAP_USER_GID_NUMBER
301         self.ldapUserHomePath = self.conn.config.LDAP_USER_HOME_PATH
302
303         self.baseDN = self.conn.ldapPeopleDN
304
305
306
307         self.ldapShell = '/bin/bash'
308
309
310
311     def LdapGenerateUniqueLogin(self, record):
312         """
313         Generate login for adding a new user in LDAP Directory
314         (four characters minimum length). Get proper last name and
315         first name so that the user's login can be generated.
316         :param record: Record must contain first_name and last_name.
317         :param record: dict
318         :return: the generated login for the user described with record if the
319         login generation is successful, None if it fails.
320         :rtype: string or None
321         """
322         #For compatibility with other ldap func
323         if 'mail' in record and 'email' not in record:
324             record['email'] = record['mail']
325
326         lower_first_name, lower_last_name =  \
327                         self.login_pwd.get_user_firstname_lastname(record)
328
329
330         index, login = self.login_pwd.choose_sets_chars_for_login( \
331                                                             lower_first_name, \
332                                                             lower_last_name)
333         login_filter = '(uid=' + login + ')'
334         get_attrs = ['uid']
335         try :
336             #Check if login already in use
337
338             while (len(self.LdapSearch(login_filter, get_attrs)) is not 0 ):
339
340                 index += 1
341                 if index >= 9:
342                     logger.error("LoginException : Generation login error \
343                                     with minimum four characters")
344                 else:
345                     try:
346                         login = \
347                                 lower_first_name[0:index] + \
348                         lower_last_name[0:self.login_pwd.login_max_length-index]
349                         login_filter = '(uid='+ login+ ')'
350                     except KeyError:
351                         print "lower_first_name - lower_last_name too short"
352
353             logger.debug("LDAP.API \t LdapGenerateUniqueLogin login %s"%(login))
354             return login
355
356         except  ldap.LDAPError, error :
357             logger.log_exc("LDAP LdapGenerateUniqueLogin Error %s" %error)
358             return None
359
360
361     def find_max_uidNumber(self):
362
363         """Find the LDAP max uidNumber (POSIX uid attribute) .
364         Used when adding a new user in LDAP Directory
365         :return: max uidNumber + 1
366         :rtype:string
367         """
368         #First, get all the users in the LDAP
369         get_attrs = "(uidNumber=*)"
370         login_filter = ['uidNumber']
371
372         result_data = self.LdapSearch(get_attrs, login_filter)
373         #It there is no user in LDAP yet, First LDAP user
374         if result_data == []:
375             max_uidnumber = self.ldapUserUidNumberMin
376         #Otherwise, get the highest uidNumber
377         else:
378
379             uidNumberList = [int(r[1]['uidNumber'][0])for r in result_data ]
380             logger.debug("LDAPapi.py \tfind_max_uidNumber  \
381                                     uidNumberList %s " %(uidNumberList))
382             max_uidnumber = max(uidNumberList) + 1
383
384         return str(max_uidnumber)
385
386
387     def get_ssh_pkey(self, record):
388         """TODO ; Get ssh public key from sfa record
389         To be filled by N. Turro ? or using GID pl way?
390
391         """
392         return 'A REMPLIR '
393
394     @staticmethod
395     #TODO Handle OR filtering in the ldap query when
396     #dealing with a list of records instead of doing a for loop in GetPersons
397     def make_ldap_filters_from_record( record=None):
398         """
399         Helper function to make LDAP filter requests out of SFA records.
400         :param record: user's sfa record. Should contain first_name,last_name,
401         email or mail, and if the record is enabled or not. If the dict
402         record does not have all of these, must at least contain the user's
403         email.
404         :type record: dict
405         :return: LDAP request
406         :rtype: string
407         """
408         req_ldap = ''
409         req_ldapdict = {}
410         if record :
411             if 'first_name' in record  and 'last_name' in record:
412                 req_ldapdict['cn'] = str(record['first_name'])+" "\
413                                         + str(record['last_name'])
414             if 'email' in record :
415                 req_ldapdict['mail'] = record['email']
416             if 'mail' in record:
417                 req_ldapdict['mail'] = record['mail']
418             if 'enabled' in record:
419                 if record['enabled'] == True :
420                     req_ldapdict['shadowExpire'] = '-1'
421                 else:
422                     req_ldapdict['shadowExpire'] = '0'
423
424             #Hrn should not be part of the filter because the hrn
425             #presented by a certificate of a SFA user not imported in
426             #Iotlab  does not include the iotlab login in it
427             #Plus, the SFA user may already have an account with iotlab
428             #using another login.
429
430
431
432             logger.debug("\r\n \t LDAP.PY make_ldap_filters_from_record \
433                                 record %s req_ldapdict %s" \
434                                 %(record, req_ldapdict))
435
436             for k in req_ldapdict:
437                 req_ldap += '('+ str(k)+ '=' + str(req_ldapdict[k]) + ')'
438             if  len(req_ldapdict.keys()) >1 :
439                 req_ldap = req_ldap[:0]+"(&"+req_ldap[0:]
440                 size = len(req_ldap)
441                 req_ldap = req_ldap[:(size-1)] +')'+ req_ldap[(size-1):]
442         else:
443             req_ldap = "(cn=*)"
444
445         return req_ldap
446
447     def make_ldap_attributes_from_record(self, record):
448         """When adding a new user to Iotlab's LDAP, creates an attributes
449         dictionnary from the SFA record understandable by LDAP.
450         Generates the user's LDAP login.
451         User is automatically validated (account enabled) and described
452         as a SFA USER FROM OUTSIDE SENSLAB'.
453         :param record: must contain the following keys and values:
454         first_name, last_name, mail, pkey (ssh key).
455         :type record: dict
456
457         :return: dictionary of attributes using LDAP data structure
458         model.
459         :rtype: dict
460
461         """
462
463         attrs = {}
464         attrs['objectClass'] = ["top", "person", "inetOrgPerson", \
465                                     "organizationalPerson", "posixAccount", \
466                                     "shadowAccount", "systemQuotas", \
467                                     "ldapPublicKey"]
468
469
470         attrs['uid'] = self.LdapGenerateUniqueLogin(record)
471         try:
472             attrs['givenName'] = str(record['first_name']).lower().capitalize()
473             attrs['sn'] = str(record['last_name']).lower().capitalize()
474             attrs['cn'] = attrs['givenName'] + ' ' + attrs['sn']
475             attrs['gecos'] = attrs['givenName'] + ' ' + attrs['sn']
476
477         except KeyError:
478             attrs['givenName'] = attrs['uid']
479             attrs['sn'] = attrs['uid']
480             attrs['cn'] = attrs['uid']
481             attrs['gecos'] = attrs['uid']
482
483
484         attrs['quota'] = self.ldapUserQuotaNFS
485         attrs['homeDirectory'] = self.ldapUserHomePath + attrs['uid']
486         attrs['loginShell'] = self.ldapShell
487         attrs['gidNumber'] = self.ldapUserGidNumber
488         attrs['uidNumber'] = self.find_max_uidNumber()
489         attrs['mail'] = record['mail'].lower()
490         try:
491             attrs['sshPublicKey'] = record['pkey']
492         except KeyError:
493             attrs['sshPublicKey'] = self.get_ssh_pkey(record)
494
495
496         #Password is automatically generated because SFA user don't go
497         #through the Iotlab website  used to register new users,
498         #There is no place in SFA where users can enter such information
499         #yet.
500         #If the user wants to set his own password , he must go to the Iotlab
501         #website.
502         password = self.login_pwd.generate_password()
503         attrs['userPassword'] = self.login_pwd.encrypt_password(password)
504
505         #Account automatically validated (no mail request to admins)
506         #Set to 0 to disable the account, -1 to enable it,
507         attrs['shadowExpire'] = '-1'
508
509         #Motivation field in Iotlab
510         attrs['description'] = 'SFA USER FROM OUTSIDE SENSLAB'
511
512         attrs['ou'] = 'SFA'         #Optional: organizational unit
513         #No info about those here:
514         attrs['l'] = 'To be defined'#Optional: Locality.
515         attrs['st'] = 'To be defined' #Optional: state or province (country).
516
517         return attrs
518
519
520
521     def LdapAddUser(self, record) :
522         """Add SFA user to LDAP if it is not in LDAP  yet.
523         :param record: dictionnary with the user's data.
524
525         :return: a dictionary with the status (Fail= False, Success= True)
526         and the uid of the newly added user if successful, or the error
527         meassage it is not. Dict has keys bool and message in case of failure,
528         and bool uid in case of success.
529         :rtype: dict
530
531         ..seealso: make_ldap_filters_from_record
532
533         """
534         logger.debug(" \r\n \t LDAP LdapAddUser \r\n\r\n ================\r\n ")
535         user_ldap_attrs = self.make_ldap_attributes_from_record(record)
536
537
538         #Check if user already in LDAP wih email, first name and last name
539         filter_by = self.make_ldap_filters_from_record(user_ldap_attrs)
540         user_exist = self.LdapSearch(filter_by)
541         if user_exist:
542             logger.warning(" \r\n \t LDAP LdapAddUser user %s %s \
543                         already exists" %(user_ldap_attrs['sn'], \
544                         user_ldap_attrs['mail']))
545             return {'bool': False}
546
547         #Bind to the server
548         result = self.conn.connect()
549
550         if(result['bool']):
551
552             # A dict to help build the "body" of the object
553
554             logger.debug(" \r\n \t LDAP LdapAddUser attrs %s " %user_ldap_attrs)
555
556             # The dn of our new entry/object
557             dn = 'uid=' + user_ldap_attrs['uid'] + "," + self.baseDN
558
559             try:
560                 ldif = modlist.addModlist(user_ldap_attrs)
561                 logger.debug("LDAPapi.py add attrs %s \r\n  ldif %s"\
562                                 %(user_ldap_attrs, ldif) )
563                 self.conn.ldapserv.add_s(dn, ldif)
564
565                 logger.info("Adding user %s login %s in LDAP" \
566                         %(user_ldap_attrs['cn'] , user_ldap_attrs['uid']))
567
568
569             except ldap.LDAPError, error:
570                 logger.log_exc("LDAP Add Error %s" %error)
571                 return {'bool' : False, 'message' : error }
572
573             self.conn.close()
574             return {'bool': True, 'uid':user_ldap_attrs['uid']}
575         else:
576             return result
577
578
579     def LdapDelete(self, person_dn):
580         """
581         Deletes a person in LDAP. Uses the dn of the user.
582         :param person_dn: user's ldap dn.
583         :type person_dn: string
584         :return: dictionary with bool True if successful, bool False
585         and the error if not.
586         :rtype:dict
587         """
588         #Connect and bind
589         result =  self.conn.connect()
590         if(result['bool']):
591             try:
592                 self.conn.ldapserv.delete_s(person_dn)
593                 self.conn.close()
594                 return {'bool': True}
595
596             except ldap.LDAPError, error:
597                 logger.log_exc("LDAP Delete Error %s" %error)
598                 return {'bool': False, 'message': error}
599
600
601     def LdapDeleteUser(self, record_filter):
602         """
603         Deletes a SFA person in LDAP, based on the user's hrn.
604         :param record_filter: Filter to find the user to be deleted. Must
605         contain at least the user's email.
606         :type record_filter: dict
607         :return: dict with bool True if successful, bool False and error message
608         otherwise
609         :rtype:dict
610         ..seealso: LdapFindUser docstring for more info on record filter.
611         ..seealso: LdapDelete for user deletion
612         """
613         #Find uid of the  person
614         person = self.LdapFindUser(record_filter, [])
615         logger.debug("LDAPapi.py \t LdapDeleteUser record %s person %s" \
616         %(record_filter, person))
617
618         if person:
619             dn = 'uid=' + person['uid'] + "," + self.baseDN
620         else:
621             return {'bool': False}
622
623         result = self.LdapDelete(dn)
624         return result
625
626
627     def LdapModify(self, dn, old_attributes_dict, new_attributes_dict):
628         """ Modifies a LDAP entry, replaces user's old attributes with
629         the new ones given.
630         :param dn: user's absolute name  in the LDAP hierarchy.
631         :param old_attributes_dict: old user's attributes. Keys must match
632         the ones used in the LDAP model.
633         :param new_attributes_dict: new user's attributes. Keys must match
634         the ones used in the LDAP model.
635         :type dn: string
636         :type old_attributes_dict: dict
637         :type new_attributes_dict: dict
638         :return: dict bool True if Successful, bool False if not.
639         :rtype:dict
640         """
641
642         ldif = modlist.modifyModlist(old_attributes_dict, new_attributes_dict)
643         # Connect and bind/authenticate
644         result = self.conn.connect()
645         if (result['bool']):
646             try:
647                 self.conn.ldapserv.modify_s(dn, ldif)
648                 self.conn.close()
649                 return {'bool' : True }
650             except ldap.LDAPError, error:
651                 logger.log_exc("LDAP LdapModify Error %s" %error)
652                 return {'bool' : False }
653
654
655     def LdapModifyUser(self, user_record, new_attributes_dict):
656         """
657         Gets the record from one user based on the user sfa record
658         and changes the attributes according to the specified new_attributes.
659         Do not use this if we need to modify the uid. Use a ModRDN
660         #operation instead ( modify relative DN )
661         :param user_record: sfa user record.
662         :param new_attributes_dict: new user attributes, keys must be the
663         same as the LDAP model.
664         :type user_record: dict
665         :type new_attributes_dict: dict
666         :return: bool True if successful, bool False if not.
667         :rtype: dict
668         ..seealso: make_ldap_filters_from_record for info on what is mandatory
669         in the user_record.
670         ..seealso: make_ldap_attributes_from_record for the LDAP objectclass.
671         """
672         if user_record is None:
673             logger.error("LDAP \t LdapModifyUser Need user record  ")
674             return {'bool': False}
675
676         #Get all the attributes of the user_uid_login
677         #person = self.LdapFindUser(record_filter,[])
678         req_ldap = self.make_ldap_filters_from_record(user_record)
679         person_list = self.LdapSearch(req_ldap, [])
680         logger.debug("LDAPapi.py \t LdapModifyUser person_list : %s" \
681                                                         %(person_list))
682         if person_list and len(person_list) > 1 :
683             logger.error("LDAP \t LdapModifyUser Too many users returned")
684             return {'bool': False}
685         if person_list is None :
686             logger.error("LDAP \t LdapModifyUser  User %s doesn't exist "\
687                         %(user_record))
688             return {'bool': False}
689
690         # The dn of our existing entry/object
691         #One result only from ldapSearch
692         person = person_list[0][1]
693         dn  = 'uid=' + person['uid'][0] + "," + self.baseDN
694
695         if new_attributes_dict:
696             old = {}
697             for k in new_attributes_dict:
698                 if k not in person:
699                     old[k] =  ''
700                 else :
701                     old[k] = person[k]
702             logger.debug(" LDAPapi.py \t LdapModifyUser  new_attributes %s"\
703                                 %( new_attributes_dict))
704             result = self.LdapModify(dn, old, new_attributes_dict)
705             return result
706         else:
707             logger.error("LDAP \t LdapModifyUser  No new attributes given. ")
708             return {'bool': False}
709
710
711
712
713     def LdapMarkUserAsDeleted(self, record):
714         """
715         Sets shadowExpire to 0, disabling the user in LDAP.
716         Calls LdapModifyUser to change the shadowExpire of the user.
717         :param record: the record of the user who has to be disabled.
718         :type record: dict
719         :return:  bool True if successful or bool False if not
720         :rtype:dict
721         ..seealso: LdapModifyUser
722         """
723
724         new_attrs = {}
725         #Disable account
726         new_attrs['shadowExpire'] = '0'
727         logger.debug(" LDAPapi.py \t LdapMarkUserAsDeleted ")
728         ret = self.LdapModifyUser(record, new_attrs)
729         return ret
730
731
732     def LdapResetPassword(self, record):
733         """
734         Resets password for the user whose record is the parameter and changes
735         the corresponding entry in the LDAP.
736
737         """
738         password = self.login_pwd.generate_password()
739         attrs = {}
740         attrs['userPassword'] = self.login_pwd.encrypt_password(password)
741         logger.debug("LDAP LdapResetPassword encrypt_password %s"\
742                     %(attrs['userPassword']))
743         result = self.LdapModifyUser(record, attrs)
744         return result
745
746
747     def LdapSearch (self, req_ldap = None, expected_fields = None ):
748         """
749         Used to search directly in LDAP, by using ldap filters and
750         return fields.
751         When req_ldap is None, returns all the entries in the LDAP.
752
753         """
754         result = self.conn.connect(bind = False)
755         if (result['bool']) :
756
757             return_fields_list = []
758             if expected_fields == None :
759                 return_fields_list = ['mail', 'givenName', 'sn', 'uid', \
760                                         'sshPublicKey', 'shadowExpire']
761             else :
762                 return_fields_list = expected_fields
763             #No specifc request specified, get the whole LDAP
764             if req_ldap == None:
765                 req_ldap = '(cn=*)'
766
767             logger.debug("LDAP.PY \t LdapSearch  req_ldap %s \
768                                     return_fields_list %s" \
769                                     %(req_ldap, return_fields_list))
770
771             try:
772                 msg_id = self.conn.ldapserv.search(
773                                             self.baseDN,ldap.SCOPE_SUBTREE,\
774                                             req_ldap, return_fields_list)
775                 #Get all the results matching the search from ldap in one
776                 #shot (1 value)
777                 result_type, result_data = \
778                                         self.conn.ldapserv.result(msg_id, 1)
779
780                 self.conn.close()
781
782                 logger.debug("LDAP.PY \t LdapSearch  result_data %s"\
783                             %(result_data))
784
785                 return result_data
786
787             except  ldap.LDAPError, error :
788                 logger.log_exc("LDAP LdapSearch Error %s" %error)
789                 return []
790
791             else:
792                 logger.error("LDAP.PY \t Connection Failed" )
793                 return
794
795
796     def _process_ldap_info_for_all_users(self, result_data):
797         """
798         Process the data of all enabled users in LDAP.
799         :param result_data: Contains information of all enabled users in LDAP
800         and is coming from LdapSearch.
801         :param result_data: list
802         ..seealso: LdapSearch
803         """
804         results = []
805         logger.debug(" LDAP.py _process_ldap_info_for_all_users result_data %s " \
806                             %(result_data))
807         for ldapentry in result_data:
808             logger.debug(" LDAP.py _process_ldap_info_for_all_users ldapentry name : %s " \
809                             %(ldapentry[1]['uid'][0]))
810             tmpname = ldapentry[1]['uid'][0]
811             hrn = self.authname + "." + tmpname
812
813             tmpemail = ldapentry[1]['mail'][0]
814             if ldapentry[1]['mail'][0] == "unknown":
815                 tmpemail = None
816
817
818             try:
819                 results.append(  {
820                         'type': 'user',
821                         'pkey': ldapentry[1]['sshPublicKey'][0],
822                         #'uid': ldapentry[1]['uid'][0],
823                         'uid': tmpname ,
824                         'email':tmpemail,
825                         #'email': ldapentry[1]['mail'][0],
826                         'first_name': ldapentry[1]['givenName'][0],
827                         'last_name': ldapentry[1]['sn'][0],
828                         #'phone': 'none',
829                         'serial': 'none',
830                         'authority': self.authname,
831                         'peer_authority': '',
832                         'pointer' : -1,
833                         'hrn': hrn,
834                         } )
835             except KeyError, error:
836                 logger.log_exc("LDAPapi.PY \t LdapFindUser EXCEPTION %s" \
837                                             %(error))
838                 return
839
840         return results
841
842     def _process_ldap_info_for_one_user(self, record, result_data):
843         """
844         Put the user's ldap data into shape. Only deals with one user
845         record and one user data from ldap.
846         :param record: user record
847         :param result_data: Raw ldap data coming from LdapSearch
848         :return: user's data dict with 'type','pkey','uid', 'email',
849         'first_name' 'last_name''serial''authority''peer_authority'
850         'pointer''hrn'
851         :type record: dict
852         :type result_data: list
853         :rtype :dict
854         """
855         #One entry only in the ldap data because we used a  filter
856         #to find one user only
857         ldapentry = result_data[0][1]
858         logger.debug("LDAP.PY \t LdapFindUser ldapentry %s" %(ldapentry))
859         tmpname = ldapentry['uid'][0]
860
861         tmpemail = ldapentry['mail'][0]
862         if ldapentry['mail'][0] == "unknown":
863             tmpemail = None
864
865         parent_hrn = None
866         peer_authority = None
867         if 'hrn' in record:
868             hrn = record['hrn']
869             parent_hrn = get_authority(hrn)
870             if parent_hrn != self.authname:
871                 peer_authority = parent_hrn
872             #In case the user was not imported from Iotlab LDAP
873             #but from another federated site, has an account in
874             #iotlab but currently using his hrn from federated site
875             #then the login is different from the one found in its hrn
876             if tmpname != hrn.split('.')[1]:
877                 hrn = None
878         else:
879             hrn = None
880
881
882
883         results =  {
884                     'type': 'user',
885                     'pkey': ldapentry['sshPublicKey'],
886                     #'uid': ldapentry[1]['uid'][0],
887                     'uid': tmpname ,
888                     'email':tmpemail,
889                     #'email': ldapentry[1]['mail'][0],
890                     'first_name': ldapentry['givenName'][0],
891                     'last_name': ldapentry['sn'][0],
892                     #'phone': 'none',
893                     'serial': 'none',
894                     'authority': parent_hrn,
895                     'peer_authority': peer_authority,
896                     'pointer' : -1,
897                     'hrn': hrn,
898                     }
899         return results
900
901
902     def LdapFindUser(self, record=None, is_user_enabled=None,
903                      expected_fields=None):
904         """
905         Search a SFA user with a hrn. User should be already registered
906         in Iotlab LDAP.
907         :param record: sfa user's record. Should contain first_name,last_name,
908         email or mail. If no record is provided, returns all the users found
909         in LDAP.
910         :type record: dict
911         :param is_user_enabled: is the user's iotlab account already valid.
912         :type is_user_enabled: Boolean.
913         :return: LDAP entries from ldap matching the filter provided. Returns
914         a single entry if one filter has been given and a list of
915         entries otherwise.
916         :rtype:  dict or list
917         """
918         custom_record = {}
919         if is_user_enabled:
920             custom_record['enabled'] = is_user_enabled
921         if record:
922             custom_record.update(record)
923
924
925         req_ldap = self.make_ldap_filters_from_record(custom_record)
926         return_fields_list = []
927         if expected_fields == None :
928             return_fields_list = ['mail', 'givenName', 'sn', 'uid', \
929                                     'sshPublicKey']
930         else :
931             return_fields_list = expected_fields
932
933         result_data = self.LdapSearch(req_ldap, return_fields_list )
934         logger.debug("LDAP.PY \t LdapFindUser  result_data %s" %(result_data))
935
936         if len(result_data) is 0:
937             return None
938         #Asked for a specific user
939         if record is not None:
940             results = self._process_ldap_info_for_one_user(record, result_data)
941
942         else:
943         #Asked for all users in ldap
944             results = self._process_ldap_info_for_all_users(result_data)
945         return results
946