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