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