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