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