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