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