summaryrefslogtreecommitdiffstats
path: root/LDAPShared.py
blob: c5e693d736eb2481ff5f63490f6514b1d2f069cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
#####################################################################
#
# LDAPShared	Class with methods shared in LDAPUserManager and
#               LDAPLoginAdapter
#
# This software is governed by a license. See
# LICENSE.txt for the terms of this license.
#
#####################################################################
__version__='$Revision$'[11:-2]

import sys, urllib, string, re
from AccessControl import ClassSecurityInfo
from Globals import MessageDialog, InitializeClass

try:
    import _ldap
except ImportError:
    import ldap
    _ldap = ldap


ldap_scopes = (_ldap.SCOPE_BASE, _ldap.SCOPE_ONELEVEL, _ldap.SCOPE_SUBTREE)

# Find out if we have unicode support and set some helpers up
try:
    import codecs
    unicode_enabled = 1
except ImportError:
    unicode_enabled = 0

    # backward-compatible conversion functions
    # Kindly provided by Florent Guillaume
    def utf8_to_latin1_subone(m):
        xy = m.group()
        v = ((ord(xy[0])-0xc0)<<6) + ord(xy[1])-0x80
        if v < 0x80: return xy # illegal UTF-8, don't translate
        return chr(v)

    def latin1_to_utf8_subone(m):
        c = m.group()
        v = ord(c)
        if v < 0x80: return c
        return chr((v>>6)+0xc0)+chr((v&0x3f)+0x80)


class LDAPShared:
    security = ClassSecurityInfo()

    def utf8_to_latin1_nonunicode(self, s):
        return self.utf8_match.sub(utf8_to_latin1_subone, s)

    def latin1_to_utf8_nonunicode(self, s):
        return self.latin1_match.sub(latin1_to_utf8_subone, s)

    def utf8_to_latin1_unicode(self, s):
        return self.encodeLatin1(self.decodeUTF8(s)[0])[0]

    def latin1_to_utf8_unicode(self, s):
        return unicode(s, 'latin1').encode('UTF-8')

    if unicode_enabled:
        encodeLatin1, decodeLatin1 = codecs.lookup('latin1')[:2]
        encodeUTF8, decodeUTF8 = codecs.lookup('UTF-8')[:2]
        latin1_to_utf8 = latin1_to_utf8_unicode
        utf8_to_latin1 = utf8_to_latin1_unicode
    else:
        utf8_match = re.compile(r'([\300-\337][\200-\277])')
        latin1_match = re.compile(r'([\200-\377])')
        latin1_to_utf8 = latin1_to_utf8_nonunicode
        utf8_to_latin1 = utf8_to_latin1_nonunicode


    def _connect(self, client_method=None, REQUEST=None):
        """ initialize an ldap server connection """
        try:
            connection = _ldap.open(self.LDAP_server,self.LDAP_port)
            connection.simple_bind_s(self._binduid,self._bindpwd)
            return connection

        except Exception, e:
            msg = '<b>Cannot connect to LDAP server %s port %s!<br> \
                   Problem: ' % (self.LDAP_server, self.LDAP_port)
            problem =  str(e.args)

            if REQUEST is not None:
                return MessageDialog(
                       title   = 'Connection problem!',
                       message = msg + problem,
                       action  = client_method)
            else:
                return None 


    def _disconnect(self, connection):
        """ close the ldap connection """
        try:
            connection.unbind_s()
        except:
            pass


    def _formatException(self, e):
        """ 
            Centralized method to format exception information 
            so it can be used inside a MessageDialog
        """
        infotuple = getattr(e, 'args', ())
        if infotuple:
            errordict = infotuple[0]
            msg = '<b>An Error occurred:<br>'
            msg = msg + 'Error Type: %s<br>Server Message: %s</b>' % (
                    errordict.get('desc', 'Not Available'),
                    errordict.get('info', 'Not Available'))

        return msg


    def _searchResults(self, search_base, search_scope, 
                       search_string, attrs=None, user=None,
                       password=None):
        """ The main search engine """

        connection = self._connect()
        if connection is None:
            return '###Error###: Cannot connect to LDAP server!'

        if user is not None and password is not None:
            try:
                connection.bind_s(user, password, _ldap.AUTH_SIMPLE)
            except _ldap.INVALID_CREDENTIALS:
                self._disconnect(connection)
                raise

        search_string = self.latin1_to_utf8(search_string)

        try:
            res = connection.search_s(search_base, search_scope, 
                                      search_string, attrs)

            for rec_dn, rec_dict in res:
                for key, value in rec_dict.items():
                    try:
                        for i in range(len(value)):
                            value[i] = self.utf8_to_latin1(value[i])
                    except:
                        pass
        
        except _ldap.NO_SUCH_OBJECT:
            res = '###ERROR###: Cannot find %s under %s!' % (
                                  search_string, search_base)
        except _ldap.SIZELIMIT_EXCEEDED:
            res = '###Error###: Too many matches for query %s under %s!' % (
                                  search_string, search_base)
        except:
            res = '###Error###: %s, %s' % sys.exc_info()[:2]

        self._disconnect(connection)

        return res


    security.declareProtected('View', 'encodeString')
    def encodeString(self, string):
        """ 
            A helper to encrypt a string so that it can be used as query 
            string piece
        """
        return urllib.quote(string)


    security.declareProtected('Manage users', 'getUserDetails')
    def getUserDetails(self, encoded_dn, format=None):
        """ Return all attributes for a given DN """
        dn = urllib.unquote(encoded_dn)

        res = self._searchResults(dn, self.users_scope, 'objectClass=*')

        if type(res) == type (''):
            result = (('Exception', res),)
        elif len(res) > 0:
            value_dict = res[0][1]

            if format == None:
                result = value_dict.items()
                result.sort()
            elif format == 'dictionary':
                result = value_dict
        else:
            result = ()

        return result


    security.declareProtected('Manage users', 'getGroupDetails')
    def getGroupDetails(self, encoded_cn):
        """ Return all group details """
        cn = urllib.unquote(encoded_cn)

        res = self._searchResults(self.groups_base, self.groups_scope,
                                    'cn=%s' % cn, ['uniqueMember', 'member'])

        if type(res) == type (''):
            result = (('Exception', res),)
        elif len(res) > 0:
            result = res[0][1].items()
            result.sort()
        else:
            result = ()

        return result


    security.declareProtected('Manage users', 'findUser')
    def findUser(self, search_param, search_term, attrs=None):
        """ Look up matching user records based on attributes """
        lscope = ldap_scopes[self.users_scope]
        users  = []

        if search_param == 'dn':
            users_base = search_term
            search_str = 'objectClass=*'
        else:
            users_base = self.users_base
            search_str = '(%s=*%s*)' % (search_param, search_term)

        res = self._searchResults( users_base
                                 , lscope
                                 , search_str
                                 , attrs
                                 )

        if type(res) == type(''):
            users = [{ 'dn' : res,
                       'sn' : 'Error' }]
        elif len(res) > 0:
            for i in range(len(res)):
                dn = res[i][0]
                rec_dict = {}
                rec_dict['dn'] = dn
                rec_dict['sn'] = ''

                for key in res[i][1].keys():
                    rec_dict[key] = res[i][1][key][0]

                users.append(rec_dict)

        return users


    security.declareProtected('Manage users', 'getGroups')
    def getGroups(self, dn='*', attr=None, pwd=None):
        """
            returns a list of possible groups from the ldap tree.
            Used e.g. in showgroups.dtml
        """
        group_list = []
        gscope = ldap_scopes[self.groups_scope]

        if pwd is not None:
            res = self._searchResults(self.groups_base, gscope,
                    "(uniquemember=%s)" % (dn),
                    attrs = ['dn', 'cn'], user=dn, password=pwd)
        else:
            res = self._searchResults(self.groups_base,gscope,
                 "(uniquemember=%s)" % (dn), attrs=['dn','cn'])

        if type(res) == type(''):
            if attr is None:
                group_list = (('', res),)
            else:
                group_list = (res,)
        elif len(res) > 0:
            for i in range(len(res)):
                dn = res[i][0]
                try:
                    cn = res[i][1]['cn'][0]
                except KeyError:    # Novell Directory Server oddity
                    cn = _ldap.explode_dn( dn, 1 )[0]

                if attr is None:
                    group_list.append( ( cn, dn ) )
                elif attr == 'cn':
                    group_list.append( cn )
                elif attr == 'dn':
                    group_list.append( dn )

        return group_list


    security.declareProtected('Manage users',
                              'getGroupsWithInconsistentRecords')
    def getGroupsWithInconsistentRecords(self, dn, pwd=None):
        """
            returns a list of groups for those applications in which
            the exact spacing or divider is not guaranteed to be the same
            between a user record DN and a uniquemember in a group record
        """
        group_list = []
        gscope = ldap_scopes[self.groups_scope]
        exploded_dn = _ldap.explode_dn(dn)

        if pwd is not None:
            res = self._searchResults(self.groups_base, gscope,
                    "(uniquemember=*)",
                    attrs = ['uniquemember', 'cn'], user=dn, password=pwd)
        else:
            res = self._searchResults(self.groups_base,gscope,
                    "(uniquemember=*)",
                    attrs=['uniquemember', 'cn'])

        for group in res:
            uniq_list = ( group[1].get('uniqueMember', [])
                        + group[1].get('uniquemember', []) )

            for uniq in uniq_list:
                if _ldap.explode_dn(uniq) == exploded_dn:
                    group_list.append(group[1]['cn'][0])
                    break

        return group_list


    security.declareProtected('View management screens', 'getProperty')
    def getProperty(self, prop_name):
        """ Get at LDAPUserManager properties """
        return getattr(self, prop_name, '')


    security.declareProtected('Manage users', 'getLDAPSchema')
    def getLDAPSchema(self):
        """ Retrieve the LDAP schema this product knows about """
        return self._ldapschema


    security.declareProtected('Change LDAPUserManager', 
                              'manage_addLDAPSchemaItem')
    def manage_addLDAPSchemaItem(self, ldap_name, friendly_name='',
                                    REQUEST=None):
        """ Add a schema item to my list of known schema items """
        schema = list( self._ldapschema )
        schema.append((ldap_name, friendly_name))
        self._ldapschema = schema
 
        if REQUEST:
            msg = 'LDAP Schema item "%s" added' % ldap_name
            return self.manage_ldapschema(self, REQUEST, 
                                          manage_tabs_message=msg)


    security.declareProtected('Change LDAPUserManager',
                              'manage_deleteLDAPSchemaItems')
    def manage_deleteLDAPSchemaItems(self, ldap_names=[], REQUEST=None):
        """ Delete schema items from my list of known schema items """
        if len(ldap_names) < 1:
            if REQUEST:
                return MessageDialog(
                            title   = 'No items selected',
                            message = 'Please select items to delete',
                            action  = 'manage_ldapschema')
            else:
                return None
 
        schema = self._ldapschema
 
        for ldap_name in ldap_names:
            for schema_tuple in schema:
                if schema_tuple[0] == ldap_name:
                    del schema[schema.index(schema_tuple)]
 
        self._ldapschema = schema
 
        if REQUEST:
            msg = 'LDAP Schema items removed'
            return self.manage_ldapschema(self, REQUEST,
                                          manage_tabs_message=msg)


    security.declareProtected('Change LDAPUserManager', 
                              'manage_editRDNAttribute')
    def manage_editRDNAttribute(self, rdn_attr, REQUEST=None):
        """ Edit the RDN attribute """
        self._rdnattr = rdn_attr

        if REQUEST:
            msg = 'RDN attribute changed to "%s"' % rdn_attr
            return self.manage_ldapschema(self, REQUEST,
                                          manage_tabs_message=msg)

InitializeClass( LDAPShared )