~leo.robol/lum/trunk

« back to all changes in this revision

Viewing changes to src/lum/ldap_protocol.py

  • Committer: Leonardo Robol
  • Date: 2010-09-19 15:31:17 UTC
  • Revision ID: git-v1:281ceece5234622daba78fabf0efaf6cc893693c
Moved source to better suit setup.py

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# -*- coding: utf-8 -*-
 
3
 
 
4
import ldap, ldap.modlist, re, ldif, sys, random
 
5
from crypt import crypt
 
6
from exceptions import *
 
7
from configuration import Configuration
 
8
 
 
9
# This is just for debug
 
10
ldifwriter = ldif.LDIFWriter(sys.stdout)
 
11
 
 
12
# Utility functions
 
13
def random_string():
 
14
    """Generate a random string to be used as salt
 
15
    for crypt()"""
 
16
    length = 9
 
17
    r = ""
 
18
    possible_letters = [ ".", "/" ]
 
19
    ff = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", 
 
20
           "j", "k", "h", "l", "m", "n", "o", "p", "q", 
 
21
           "r", "s", "t", "u", "v", "w", "x", "y", "z" ]
 
22
    possible_letters.extend(ff)
 
23
    possible_letters.extend(map(lambda x : x.capitalize(), ff))
 
24
    for letter in range(0,length):
 
25
        r += random.choice(possible_letters)
 
26
    return r
 
27
 
 
28
    
 
29
 
 
30
class UserModel():
 
31
 
 
32
    def __init__(self, ldap_output = None):
 
33
 
 
34
        # Create an internatl dictionary that represent
 
35
        # ldap data
 
36
        self.__ldif = dict ()
 
37
        self.__dn = None
 
38
 
 
39
        # If ldap_output is a UserModel object use __init__()
 
40
        # as a copy constructor
 
41
        if isinstance(ldap_output, UserModel):
 
42
            dn = ldap_output.get_dn()
 
43
            dictionary = ldap_output.to_ldif()
 
44
 
 
45
            # Copy internal objects
 
46
            self.__ldif = dict(dictionary)
 
47
            self.__dn = str(dn)
 
48
 
 
49
            # Give reference to originating object away
 
50
            ldap_output = None
 
51
    
 
52
        # If ldap_output is passed to UserModel and is not
 
53
        # a UserModel istance we can assume
 
54
        # to build an object representing a real LdapObject
 
55
        if ldap_output is not None:
 
56
            self.__dn = ldap_output[0]
 
57
            self.__ldif = dict(ldap_output[1])
 
58
 
 
59
            # If you didn't set a name we can set it
 
60
            # to an empty string
 
61
            if not ldap_output[1].has_key("givenName"):
 
62
                self.set_given_name("")
 
63
            
 
64
        # If that is not the case we shall give the object
 
65
        # a Class that usually represent users in Ldap databases
 
66
        else:
 
67
            self.__ldif['objectClass'] = ["inetOrgPerson",
 
68
                                          "posixAccount",
 
69
                                          "person",
 
70
                                          "shadowAccount",
 
71
                                          "organizationalPerson",
 
72
                                          "top"]
 
73
                                                  
 
74
    def __getitem__(self, item):
 
75
        """Deprecated method to get item from the
 
76
        internal dictionary"""
 
77
        return self.__ldif[item]
 
78
        
 
79
    def __setitem__(self, item, value):
 
80
        """Deprecated method to set item in the
 
81
        internal dictionary"""
 
82
        self.__ldif[item] = value
 
83
    
 
84
    def get_dn(self):
 
85
        """Return the distinguished name of the object.
 
86
        Need some work to do the right thing."""
 
87
        return self.__dn
 
88
        
 
89
    def set_dn(self, dn):
 
90
        """Set Distinguished name of the Ldap object"""
 
91
        self.__dn = dn
 
92
    
 
93
    def get_uid(self):
 
94
        """Return the uid number of the user, or 0
 
95
        if it is not set"""
 
96
        if self.__ldif.has_key("uidNumber"):
 
97
            return self.__ldif['uidNumber'][0]
 
98
        else:
 
99
            return str(0)
 
100
        
 
101
    def set_uid(self, uid):
 
102
        """Set uid of the user. Setting it to zero
 
103
        means to autodetermine the first free uid when
 
104
        necessary"""
 
105
        self.__ldif['uidNumber'] = [str(uid)]
 
106
        
 
107
    def get_username(self):
 
108
        """Return username of the user"""
 
109
        return self.__ldif['uid'][0]
 
110
    
 
111
    def set_username(self, username):
 
112
        """Set username of the user"""
 
113
        self.__ldif['uid'] = [str(username)]
 
114
        
 
115
    def get_gid(self):
 
116
        """Return gid number. You shall use the
 
117
        method ldap_protocol.group_from_gid to
 
118
        get the group_name"""
 
119
        return self.__ldif['gidNumber'][0]
 
120
        
 
121
    def set_gid(self, gid):
 
122
        """Set the gid of the user"""
 
123
        self.__ldif['gidNumber'] = [str(gid)]
 
124
    
 
125
    def get_given_name(self):
 
126
        """Get the given name (i.e. the name)
 
127
        of the user"""
 
128
        return self.__ldif['givenName'][0]
 
129
        
 
130
    def get_gecos(self):
 
131
        """Return a complete name of the user, such
 
132
        as Tim Smith"""
 
133
        return self.__ldif['gecos'][0]
 
134
        
 
135
    def set_given_name(self, given_name):
 
136
        """Set the given name (i.e. the name) of the
 
137
        user"""
 
138
        self.__ldif['givenName'] = [str(given_name)]
 
139
        self.__ldif['cn'] = [str(given_name)]
 
140
 
 
141
        # Gecos shall be in the form name + " " + surname, but
 
142
        # if surname is not set, set only the name
 
143
        if self.__ldif.has_key("sn"):
 
144
            self.__ldif['gecos'] = [str(self.__ldif['givenName'][0] + " " + 
 
145
                                    self.__ldif['sn'][0]).strip()]
 
146
        else:
 
147
            self.__ldif['gecos'] = [str(given_name)]
 
148
    
 
149
    def get_surname(self):
 
150
        """Return surname of the user"""
 
151
        return self.__ldif['sn'][0]
 
152
        
 
153
    def set_surname(self, sn):
 
154
        """Set surname of the user"""
 
155
        self.__ldif['sn'] = [str(sn)]
 
156
 
 
157
        # Set gecos, including name if it is available
 
158
        if self.__ldif.has_key("givenName"):
 
159
            self.__ldif['gecos'] = [str(self.__ldif['givenName'][0] + " " + 
 
160
                                    self.__ldif['sn'][0]).strip()]
 
161
        else:
 
162
            self.__ldif['gecos'] = [str(sn)]
 
163
        
 
164
    def get_home(self):
 
165
        """Returns home directory of the user"""
 
166
        return self.__ldif['homeDirectory'][0]
 
167
        
 
168
    def set_home(self, home):
 
169
        """Set home directory of the user"""
 
170
        self.__ldif['homeDirectory'] = [str(home)]
 
171
        
 
172
    def get_shell(self):
 
173
        """Return the shell of the user"""
 
174
        return self.__ldif['loginShell'][0]
 
175
    
 
176
    def set_shell(self, shell):
 
177
        """Set the shell of the user"""
 
178
        self.__ldif['loginShell'] = [str(shell)]
 
179
 
 
180
    def set_password(self, password, crypt_strategy = "CRYPT"):
 
181
        """Set userPassword field of the user. Only CRYPT
 
182
        is supported at the moment being"""
 
183
        if (crypt_strategy == "CRYPT"):
 
184
            salt = "$1$%s$" % random_string()
 
185
            value = "{CRYPT}" + crypt(password, salt)
 
186
            self.__ldif['userPassword'] = [value]
 
187
        else:
 
188
            raise LumUnsupportedError('$s crypt strategy is not supported')
 
189
        
 
190
    def __str__(self):
 
191
        return "%s a.k.a %s %s" % (self.get_uid(), 
 
192
                                    self.get_given_name(), 
 
193
                                    self.get_surname())
 
194
    def to_ldif(self):
 
195
        """Return a dictionary to be passed to ldap
 
196
        methods"""
 
197
        return dict(self.__ldif)
 
198
 
 
199
 
 
200
class Connection():
 
201
 
 
202
    def __init__(self, uri, bind_dn, password, base_dn, users_ou, groups_ou):
 
203
        """
 
204
        Create a connection to the specified uri
 
205
        """
 
206
        
 
207
        # Save data
 
208
        self.__uri = uri
 
209
        self.__bind_dn = bind_dn
 
210
        self.__base_dn = base_dn
 
211
        self.__password = password
 
212
        self.__users_ou = users_ou
 
213
        self.__groups_ou = groups_ou
 
214
        
 
215
        self.__ldap = ldap.initialize(uri)
 
216
 
 
217
        # Bind to the database with the provided credentials
 
218
        try:
 
219
            self.__ldap.simple_bind_s(bind_dn, password)
 
220
        except Exception, e:
 
221
            raise LumError('Error connecting to the server, check your credentials')
 
222
        
 
223
        # Check if there are missing ou and add them
 
224
        if not self.is_present(self.__users_ou):
 
225
            print "Adding missing OrganizationalUnit: %s" % self.__users_ou
 
226
            self.add_ou(self.__users_ou)
 
227
        
 
228
        if not self.is_present(self.__groups_ou):
 
229
            print "Adding missing OrganizationalUnit: %s" % self.__groups_ou
 
230
            self.add_ou(self.__groups_ou)
 
231
 
 
232
    def add_user(self, user, modify = False):
 
233
        """
 
234
        if result_type == ldap.RES_SEARCH_RESULT:
 
235
            raise StopIteration
 
236
        Add a user to the LDAP database
 
237
        """
 
238
        users_ou = self.__users_ou
 
239
        if not self.is_present(users_ou):
 
240
            raise LumError("users organizational unit not present!")
 
241
            
 
242
        # This means that we have to autodetermine the first free uid
 
243
        if (int(user.get_uid()) == 0):
 
244
            user.set_uid(self.next_free_uid())
 
245
        
 
246
        # Distinguished name
 
247
        dn = "uid=%s,%s" % (user['uid'][0], users_ou)
 
248
 
 
249
        self.__ldap.add_s(dn, ldap.modlist.addModlist(user.to_ldif()))
 
250
            
 
251
    def modify_user(self, old_user, new_user):
 
252
        """Modify existing user (i.e. replace it)"""
 
253
        users_ou = self.__users_ou
 
254
        if not self.is_present(users_ou):
 
255
            raise LumError("users organizational unit not present!")
 
256
        
 
257
        # Distinguished name
 
258
        new_dn = "uid=%s,%s" % (new_user.get_username(), users_ou)
 
259
        old_dn = "uid=%s,%s" % (old_user.get_username(), users_ou)
 
260
        
 
261
        self.__ldap.modify_s(old_dn, ldap.modlist.modifyModlist(old_user.to_ldif(), new_user.to_ldif()))
 
262
    
 
263
    def add_group(self, group_name):
 
264
        """Add a new group, autodetermining gid."""
 
265
        groups_ou = self.__groups_ou
 
266
        
 
267
        dn = "cn=%s,%s" % (group_name, groups_ou)
 
268
        
 
269
        group_ldif = {
 
270
            'cn': [str(group_name)],
 
271
            'gidNumber': [str(self.next_free_gid())],
 
272
            'objectClass': ['posixGroup', 'top'],
 
273
        }
 
274
        
 
275
        self.__ldap.add_s(dn, ldap.modlist.addModlist(group_ldif))
 
276
        
 
277
    def next_free_uid(self):
 
278
        """Determine next free uid"""
 
279
        users = self.get_users()
 
280
        uids = map(lambda x : int(x['uidNumber'][0]), users)
 
281
        if len(uids) == 0:
 
282
            return 1100
 
283
        return (max(uids) + 1)
 
284
        
 
285
    def next_free_gid(self):
 
286
        """Determine next free gid"""
 
287
        groups_ou = self.__groups_ou
 
288
        groups = self.__ldap.search_s(groups_ou, ldap.SCOPE_ONELEVEL, "cn=*")
 
289
        
 
290
        if len(groups) == 0: 
 
291
            return 1100
 
292
        
 
293
        gids = map(lambda x : int(x[1]['gidNumber'][0]), groups)
 
294
        
 
295
        
 
296
        return (max(gids) + 1)
 
297
    
 
298
    def delete_user(self, user):
 
299
        """
 
300
        Delete an user given the uid or the UserModel
 
301
        """
 
302
        self.__ldap.delete_s(user)
 
303
 
 
304
    def change_password(self, uid, password):
 
305
        """Change password of selected user. If called when
 
306
        user has no password it will set the passord, making
 
307
        login possible."""
 
308
 
 
309
        # Get the user from LDAP
 
310
        user = self.get_user(uid)
 
311
 
 
312
        # Create a copy of the old user and change
 
313
        # its password
 
314
        new_user = UserModel(user)
 
315
        new_user.set_password(password)
 
316
 
 
317
        # Apply
 
318
        self.modify_user(user, new_user)
 
319
        
 
320
 
 
321
    def is_user_present(self, username):
 
322
        """
 
323
        Test if user is present
 
324
        """
 
325
        return self.is_present ("uid=%s" % username)
 
326
    
 
327
    def gid_from_group(self, group_name):
 
328
        """Return gid of the given group"""
 
329
        if self.__ldap is not None:
 
330
            groups = self.__ldap.search_s(self.__base_dn,
 
331
                                          ldap.SCOPE_SUBTREE, "cn=%s" % group_name)
 
332
            if len(groups) == 0:
 
333
                return None
 
334
            else:
 
335
                return groups[0][1]['gidNumber'][0]
 
336
        else:
 
337
            return None
 
338
    
 
339
    def group_from_gid(self, gid):
 
340
        """Return group name"""
 
341
        if self.__ldap is not None:
 
342
            group = self.__ldap.search_s(self.__groups_ou,
 
343
                                         ldap.SCOPE_ONELEVEL, "gidNumber=%d" % int(gid))
 
344
            if len(group) == 0:
 
345
                return "unknown"
 
346
            return group[0][1]['cn'][0]
 
347
 
 
348
    def is_present(self, ob):
 
349
        """
 
350
        Test if object is present in database
 
351
        """
 
352
        # Strip base_dn
 
353
        ob = ob.split(",")[0]
 
354
        res = self.__ldap.search_s(self.__base_dn, ldap.SCOPE_SUBTREE, ob)
 
355
        if len(res) == 0:
 
356
            return False
 
357
        return True
 
358
        
 
359
    def add_ou(self, ou):
 
360
        """
 
361
        Add an organizationalUnit to the database.
 
362
        Usage example: add_ou("People")
 
363
        """
 
364
        ldif = dict()
 
365
        ldif['objectClass'] = ['organizationalUnit',
 
366
                               'top']
 
367
        ldif['ou'] = [re.findall(r"ou=(\w+),", ou)[0]]
 
368
        self.__ldap.add_s(ou, ldap.modlist.addModlist(ldif))
 
369
        
 
370
    def get_user(self, uid):
 
371
        users = self.__ldap.search_s(self.__users_ou, ldap.SCOPE_ONELEVEL, "uid=%s" % uid)
 
372
        try:
 
373
            return UserModel(users[0])
 
374
        except IndexError:
 
375
            raise LumUserNotFoundError("User %s not found in LDAP" % uid)
 
376
 
 
377
    def get_users(self, key = None):
 
378
        """
 
379
        Get user that match the given key or all users if
 
380
        key is not given.
 
381
        """
 
382
        msgid = self.__ldap.search(self.__users_ou, ldap.SCOPE_ONELEVEL, "uid=*")
 
383
        return UserIterator(self.__ldap, msgid)
 
384
        
 
385
    def get_groups(self, key = None):
 
386
        if key is None:
 
387
            key = "*"
 
388
        groups = map(lambda x : x[1], self.__ldap.search_s(self.__groups_ou, ldap.SCOPE_ONELEVEL, "cn=%s" % key))
 
389
        ret = dict()
 
390
        for group in groups:
 
391
            ret[group['gidNumber'][0]] = group['cn'][0]
 
392
        return ret
 
393
        
 
394
class UserIterator():
 
395
    """UserIterator permits to extract ldap query
 
396
    result while the ldap_module is still searching, 
 
397
    reducing search time"""
 
398
 
 
399
    def __init__(self, ldap_connection, msgid):
 
400
        self.__ldap = ldap_connection
 
401
        self.__msgid = msgid
 
402
        
 
403
    def __iter__(self):
 
404
        return self
 
405
        
 
406
    def next(self):
 
407
        result_type, data = self.__ldap.result(msgid = self.__msgid, all = 0)
 
408
        for user in data:
 
409
            return UserModel(user)
 
410
        if result_type == ldap.RES_SEARCH_RESULT:
 
411
            raise StopIteration
 
412
        
 
413