69d993b53c914213b6c633f1d3507d3925ac52e6
[sfa.git] / sfa / senslab / LDAPapi.py
1
2 import string
3 import random
4 from passlib.hash import ldap_salted_sha1 as lssha
5 from sfa.util.xrn import Xrn,get_authority 
6 import ldap
7 from sfa.util.config import Config
8 #from sfa.trust.gid import *
9 from sfa.trust.hierarchy import Hierarchy
10 #from sfa.trust.auth import *
11 from sfa.trust.certificate import *
12 import ldap.modlist as modlist
13 from sfa.util.sfalogging import logger
14
15
16 #API for OpenLDAP
17
18 class ldap_co:
19     """ Set admin login and server configuration variables."""
20     def __init__(self):
21         
22         self.login = 'cn=admin,dc=senslab,dc=info'
23         self.passwd = 'sfa'  
24         self.server_ip = "192.168.0.251"
25
26     
27     def connect(self, bind = True):
28         """Enables connection to the LDAP server.
29         Set the bind parameter to True if a bind is needed
30         (for add/modify/delete operations).
31         Set to False otherwise.
32         
33         """
34         try:
35             self.ldapserv = ldap.open(self.server_ip)
36         except ldap.LDAPError, e:
37             return {'bool' : False, 'message' : e }
38         
39         # Bind with authentification
40         if(bind): 
41             return self.bind()
42         
43         else:     
44             return {'bool': True}
45     
46     
47     def bind(self):
48         """ Binding method. """
49         try:
50             # Opens a connection after a call to ldap.open in connect:
51             self.ldapserv = ldap.initialize("ldap://" + self.server_ip )
52                 
53             # Bind/authenticate with a user with apropriate rights to add objects
54             self.ldapserv.simple_bind_s(self.login, self.passwd)
55
56         except ldap.LDAPError, e:
57             return {'bool' : False, 'message' : e }
58
59         return {'bool': True}
60     
61     def close(self):
62         """ Close the LDAP connection """
63         try:
64             self.ldapserv.unbind_s()
65         except ldap.LDAPError, e:
66             return {'bool' : False, 'message' : e }
67             
68         
69 class LDAPapi :
70     def __init__(self):
71             
72         #SFA related config
73         self.senslabauth=Hierarchy()
74         config=Config()
75         self.authname=config.SFA_REGISTRY_ROOT_AUTH
76         #authinfo=self.senslabauth.get_auth_info(self.authname)
77         self.charsPassword = [ '!','$','(',')','*','+',',','-','.',\
78                                 '0','1','2','3','4','5','6','7','8','9',\
79                                 'A','B','C','D','E','F','G','H','I','J',\
80                                 'K','L','M','N','O','P','Q','R','S','T',\
81                                 'U','V','W','X','Y','Z','_','a','b','c',\
82                                 'd','e','f','g','h','i','j','k','l','m',\
83                                 'n','o','p','q','r','s','t','u','v','w',\
84                                 'x','y','z','\'']
85         self.lengthPassword = 8;
86         
87         #self.auth=Auth()
88         #gid=authinfo.get_gid_object()
89         #self.ldapdictlist = ['type',
90                         #'pkey',
91                         #'uid',
92                         #'serial',
93                         #'authority',
94                         #'peer_authority',
95                         #'pointer' ,
96                         #'hrn']
97         self.baseDN = "ou=people,dc=senslab,dc=info"
98         self.conn =  ldap_co()    
99                         
100     
101     def generate_login(self, record):
102         """Generate login for adding a new user in LDAP Directory 
103         (four characters minimum length)
104         Record contains first name and last name.
105         
106         """ 
107         #Remove all special characters from first_name/last name
108         lower_first_name = record['first_name'].replace('-','')\
109                                         .replace('_','').replace('[','')\
110                                         .replace(']','').replace(' ','')\
111                                         .lower()
112         lower_last_name = record['last_name'].replace('-','')\
113                                         .replace('_','').replace('[','')\
114                                         .replace(']','').replace(' ','')\
115                                         .lower()  
116         length_last_name = len(lower_last_name)
117         login_max_length = 8
118         
119         #Try generating a unique login based on first name and last name
120         getAttrs = ['uid']
121         if length_last_name >= login_max_length :
122             login = lower_last_name[0:login_max_length]
123             index = 0;
124             logger.debug("login : %s index : %s" %login %index);
125         elif length_last_name >= 4 :
126             login = lower_last_name
127             index = 0
128             logger.debug("login : %s index : %s" %login %index);
129         elif length_last_name == 3 :
130             login = lower_first_name[0:1] + lower_last_name
131             index = 1
132             logger.debug("login : %s index : %s" %login %index);
133         elif length_last_name == 2:
134             if len ( lower_first_name) >=2:
135                 login = lower_first_name[0:2] + lower_last_name
136                 index = 2
137                 logger.debug("login : %s index : %s" %login %index);
138             else:
139                 logger.error("LoginException : \
140                             Generation login error with \
141                             minimum four characters")
142             
143                 
144         else :
145             logger.error("LDAP generate_login failed : \
146                             impossible to generate unique login for %s %s" \
147                             %lower_first_name %lower_last_name)
148             
149         filter = '(uid='+ login+ ')'
150         try :
151             #Check if login already in use
152             while (self.ldapSearch(filter, getAttrs) is not [] ):
153             
154                 index += 1
155                 if index >= 9:
156                     logger.error("LoginException : Generation login error \
157                                     with minimum four characters")
158                 else:
159                     try:
160                         login = lower_first_name[0,index] + \
161                                     lower_last_name[0,login_max_length-index]
162                         filter = '(uid='+ login+ ')'
163                     except KeyError:
164                         print "lower_first_name - lower_last_name too short"
165             return login
166                     
167         except  ldap.LDAPError,e :
168             logger.log_exc("LDAP generate_login Error %s" %e)
169             #print >>sys.stderr, "ERROR LDAP %s" %(e)   
170         
171         
172         
173     def generate_password(self):
174     
175         """Generate password for adding a new user in LDAP Directory 
176         (8 characters length) return password
177         
178         """
179         password = str()
180         for index in range(self.lengthPassword):
181             password += self.charsPassword[random.randint(0, \
182                                             len(self.charsPassword))]
183
184         return password
185
186     def encrypt_password(self, password):
187        """ Use passlib library to make a RFC2307 LDAP encrypted password
188        salt size = 8, use sha-1 algorithm. Returns encrypted password.
189        
190        """
191        #Keep consistency with Java Senslab's LDAP API 
192        #RFC2307SSHAPasswordEncryptor so set the salt size to 8 bytres
193        return lssha.encrypt(password,salt_size = 8)
194
195     def find_max_uidNumber(self):
196             
197         """Find the LDAP max uidNumber (POSIX uid attribute) .
198         Used when adding a new user in LDAP Directory 
199         returns integer max uidNumber + 1
200         
201         """
202         #Get all the users in the LDAP
203         ldapUserUidNumberMin = 2000 
204
205         getAttrs = "(uidNumber=*)"
206         filter = ['uidNumber']
207
208         result_data = self.ldapSearch(getAttrs, filter) 
209         #First LDAP user
210         if result_data == []:
211             max_uidnumber = ldapUserUidNumberMin
212         #Get the highest uidNumber
213         else:
214             uidNumberList = [r[1]['uidNumber'] for r in result_data ]
215             max_uidnumber = max(uidNumberList) + 1
216             
217         return max_uidnumber
218                     
219         
220     def make_ldap_attributes_from_record(self, record):
221         """When addind a new user to Senslab's LDAP, creates an attributes 
222         dictionnary from the SFA record.
223         
224         """
225
226         attrs = {}
227         attrs['objectClass'] = ["top", "person", "inetOrgPerson",\
228                                     "organizationalPerson", "posixAccount",\
229                                     "shadowAccount", "systemQuotas",\
230                                     "ldapPublicKey"]
231         
232         attrs['givenName'] = str(record['first_name']).lower(),capitalize()
233         attrs['sn'] = str(record['last_name']).lower().capitalize()
234         attrs['cn'] = attrs['givenName'] + ' ' + attrs['sn']
235         attrs['gecos'] = attrs['givenName'] + ' ' + attrs['sn']
236         attrs['uid'] = self.generate_login(record)   
237                     
238         attrs['quota'] = '/dev/vdb:2000000:2500000:0:0'
239         attrs['homeDirectory'] = '/senslab/users/' + attrs['uid']
240         attrs['loginShell'] = '/senslab/users/.ssh/welcome.sh'
241         attrs['gidNumber'] = '2000'     
242         attrs['uidNumber'] = str(self.find_max_uidNumber())
243         attrs['mail'] = record['mail'].lower()
244         attrs['sshPublicKey'] = record['sshpkey']  #To be filled by N. Turro
245         attrs['description'] = 'SFA USER FROM OUTSIDE SENSLAB'
246         #TODO  TO BE FILLED 
247         password = self.generate_password()
248         attrs['userPassword']= self.encrypt_password(password)
249         
250         return attrs
251     
252     def ldapAdd(self, record = None) :
253         """Add SFA user to LDAP if it is not in LDAP  yet. """
254         
255         user_ldap_attrs = self.make_ldap_attributes_from_record(record)
256
257         
258         #Check if user already in LDAP wih email, first name and last name
259         filter_by = self.make_ldap_filters_from_record(user_ldap_attrs)
260         user_exist = self.ldapSearch(filter_by)
261         if user_exist:
262             logger.warning(" \r\n \t LDAP ldapAdd user %s %s already exists" \
263                             %(user_ldap_attrs['sn'],user_ldap_attrs['mail'] ) 
264             return {'bool': False}
265         
266         #Bind to the server
267         result = self.conn.connect()
268         
269         if(result['bool']):
270             
271             # A dict to help build the "body" of the object
272             
273             logger.debug(" \r\n \t LDAP ldapAdd attrs %s " %user_ldap_attrs)
274
275             # The dn of our new entry/object
276             dn = 'uid=' + user_ldap_attrs['uid'] + "," + self.baseDN 
277
278             try:
279                 ldif = modlist.addModlist(user_ldap_attrs)
280                 logger.debug("\r\n \tLDAPapi.PY add attrs %s \r\n  ldif %s"\
281                                 %(user_ldap_attrs,ldif) )
282                 self.conn.ldapserv.add_s(dn,ldif)
283                 
284                 logger.info("Adding user %s login %s in LDAP" \
285                         %user_ldap_attrs['cn'] %user_ldap_attrs['uid'])
286                         
287                         
288             except ldap.LDAPError, e:
289                 logger.log_exc("LDAP Add Error %s" %e)
290                 return {'bool' : False, 'message' : e }
291         
292             self.conn.close()
293             return {'bool': True}  
294         else: 
295             return result
296
297         
298     def ldapDelete(self, person_dn):
299         """
300         Deletes a person in LDAP. Uses the dn of the user.
301         """
302         #Connect and bind   
303         result =  self.conn.connect()
304         if(result['bool']):
305             try:
306                 self.conn.ldapserv.delete_s(person_dn)
307                 self.conn.close()
308                 return {'bool': True}
309             
310             except ldap.LDAPError, e:
311                 logger.log_exc("LDAP Delete Error %s" %e)
312                 return {'bool': False}
313         
314     
315     def ldapDeleteHrn(self, record_filter): 
316         """
317         Deletes a SFA person in LDAP, based on the user's hrn.
318         """
319         #Find uid of the  person 
320         person = self.ldapFindHrn(record_filter)
321         
322         if person:
323             dn = 'uid=' + person['uid'] + "," +self.baseDN 
324         else:
325             return {'bool': False}
326         
327         result = self.ldapDelete(dn)
328         return result
329         
330                 
331                 
332     def ldapModify(self, record_filter, new_attributes):
333         """
334         Gets the record from one user based on record_filter 
335         and changes the attributes according to the specified new_attributes.
336         Does not use this if we need to modify the uid. Use a ModRDN 
337         #operation instead ( modify relative DN )
338         """
339         
340         person = self.ldapFindHrn(record_filter,[] )
341         if person:
342             # The dn of our existing entry/object
343             dn  = 'uid=' + person['uid'] + "," +self.baseDN 
344         else:
345             return
346         
347         if new_attributes:
348             old = {}
349             for k in new_attributes:
350                 old[k] =  person[k]
351                 
352             ldif = modlist.modifyModlist(old,new_attributes)
353             
354             # Connect and bind/authenticate    
355             result = self.conn.connect(bind) 
356             if (result['bool']): 
357                 try:
358                     self.conn.ldapserver.modify_s(dn,ldif)
359                     self.conn.close()
360                 except ldap.LDAPError, e:
361                     logger.log_exc("LDAP ldapModify Error %s" %e)
362                     return {'bool' : False }
363             
364             return {'bool': True}  
365             
366             
367             
368     #TODO Handle OR filtering in the ldap query when 
369     #dealing with a list of records instead of doing a for loop in GetPersons   
370     def make_ldap_filters_from_record(self, record=None):
371         """
372         Helper function to make LDAP filter requests out of SFA records.
373         """
374         req_ldapdict = {}
375         if record :
376             if 'first_name' in record  and 'last_name' in record:
377                 req_ldapdict['cn'] = str(record['first_name'])+" "\
378                                         + str(record['last_name'])
379             if 'email' in record  or 'mail' in record:
380                 req_ldapdict['mail'] = record['email']
381             if 'hrn' in record :
382                 splited_hrn = record['hrn'].split(".")
383                 if splited_hrn[0] != self.authname :
384                     logger.warning(" \r\n LDAP.PY \
385                         make_ldap_filters_from_record I know nothing \
386                         about %s my authname is %s not %s" \
387                         %(record['hrn'], self.authname, splited_hrn[0]) )
388                         
389                 login=splited_hrn[1]
390                 req_ldapdict['uid'] = login
391             
392             req_ldap=''
393             logger.debug("\r\n \t LDAP.PY make_ldap_filters_from_record \
394                                 record %s req_ldapdict %s" \
395                                 %(record, req_ldapdict))
396             
397             for k in req_ldapdict:
398                 req_ldap += '('+str(k)+'='+str(req_ldapdict[k])+')'
399             if  len(req_ldapdict.keys()) >1 :
400                 req_ldap = req_ldap[:0]+"(&"+req_ldap[0:]
401                 size = len(req_ldap)
402                 req_ldap= req_ldap[:(size-1)] +')'+ req_ldap[(size-1):]
403         else:
404             req_ldap = "(cn=*)"
405         
406         return req_ldap
407
408         
409         
410
411     def ldapSearch (self, req_ldap = None, expected_fields = None ):
412         """
413         Used to search directly in LDAP, by using ldap filters and
414         return fields. 
415         When req_ldap is None, returns all the entries in the LDAP.
416         """
417         result = self.conn.connect(bind = False)
418         if (result['bool']) :
419             
420             return_fields = []
421             if expected_fields == None : 
422                 return_fields = ['mail','givenName', 'sn', 'uid','sshPublicKey']
423             else : 
424                 return_fields = expected_fields
425                 
426             logger.debug("LDAP.PY \t ldapSearch  req_ldap %s \
427                             return_fields %s" %(req_ldap,return_fields))
428
429             try:
430                 msg_id = self.conn.ldapserv.search(
431                                             self.baseDN,ldap.SCOPE_SUBTREE,\
432                                             req_ldap,return_fields)     
433                 #Get all the results matching the search from ldap in one 
434                 #shot (1 value)
435                 result_type, result_data = \
436                                         self.conn.ldapserv.result(msg_id,1)
437
438                 self.conn.close()
439
440                 logger.debug("LDAP.PY \t ldapSearch  result_data %s"\
441                             %(result_data))
442
443                 return result_data
444             
445             except  ldap.LDAPError,e :
446                 logger.log_exc("LDAP ldapSearch Error %s" %e)
447                 return []
448             
449             else:
450                 logger.error("LDAP.PY \t Connection Failed" )
451                 return 
452             
453
454     def ldapFindHrn(self,record = None, expected_fields = None):
455         """
456         Search a SFA user with a hrn. User should be already registered 
457         in Senslab LDAP. 
458         Returns one matching entry 
459         """   
460
461         req_ldap = self.make_ldap_filters_from_record(record) 
462         return_fields = []
463         if expected_fields == None : 
464             return_fields = ['mail','givenName', 'sn', 'uid','sshPublicKey']
465         else : 
466             return_fields = expected_fields
467             
468         result_data = self.ldapSearch(req_ldap,  return_fields )
469             
470         if result_data is None:
471                 return None
472         #Asked for a specific user
473         if record :
474             ldapentry = result_data[0][1]
475             logger.debug("LDAP.PY \t ldapFindHrn ldapentry %s" %(ldapentry))
476             tmpname = ldapentry['uid'][0]
477
478             tmpemail = ldapentry['mail'][0]
479             if ldapentry['mail'][0] == "unknown":
480                 tmpemail = None
481                 
482             try:
483                 hrn = record['hrn']
484                 parent_hrn = get_authority(hrn)
485                 peer_authority = None
486                 if parent_hrn is not self.authname:
487                     peer_authority = parent_hrn
488                     
489
490                             
491                 results=  {     
492                             'type': 'user',
493                             'pkey': ldapentry['sshPublicKey'][0],
494                             #'uid': ldapentry[1]['uid'][0],
495                             'uid': tmpname ,
496                             'email':tmpemail,
497                             #'email': ldapentry[1]['mail'][0],
498                             'first_name': ldapentry['givenName'][0],
499                             'last_name': ldapentry['sn'][0],
500                             #'phone': 'none',
501                             'serial': 'none',
502                             'authority': parent_hrn,
503                             'peer_authority': peer_authority,
504                             'pointer' : -1,
505                             'hrn': hrn,
506                             }
507             except KeyError:
508                 lorrer.log_exc("LDAPapi \t ldapSearch KEyError results %s" \
509                                 %(results) )
510                 pass 
511         else:
512         #Asked for all users in ldap
513             results = []
514             for ldapentry in result_data:
515                 logger.debug(" LDAP.py ldapFindHrn ldapentry name : %s " \
516                                 %(ldapentry[1]['uid'][0]))
517                 tmpname = ldapentry[1]['uid'][0]
518                 hrn=self.authname+"."+ tmpname
519                 
520                 tmpemail = ldapentry[1]['mail'][0]
521                 if ldapentry[1]['mail'][0] == "unknown":
522                     tmpemail = None
523
524         
525                 parent_hrn = get_authority(hrn)
526                 parent_auth_info = self.senslabauth.get_auth_info(parent_hrn)
527                 try:
528                     results.append(  {  
529                             'type': 'user',
530                             'pkey': ldapentry[1]['sshPublicKey'][0],
531                             #'uid': ldapentry[1]['uid'][0],
532                             'uid': tmpname ,
533                             'email':tmpemail,
534                             #'email': ldapentry[1]['mail'][0],
535                             'first_name': ldapentry[1]['givenName'][0],
536                             'last_name': ldapentry[1]['sn'][0],
537                             #'phone': 'none',
538                             'serial': 'none',
539                             'authority': self.authname,
540                             'peer_authority': '',
541                             'pointer' : -1,
542                             'hrn': hrn,
543                             } ) 
544                 except KeyError:
545                     pass
546         return results   
547