source: directoryauthplugin/trunk/tracext/dirauth/auth.py

Last change on this file was 17358, checked in by Ryan J Ollos, 4 years ago

Remove unnecessary line terminators

File size: 30.2 KB
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2007 John Hampton <pacopablo@pacopablo.com>
4# All rights reserved.
5#
6# This software is licensed as described in the file COPYING, which
7# you should have received as part of this distribution.
8#
9# Author: John Hampton <pacopablo@pacopablo.com>
10# Extended: Branson Matheson <branson.matheson@nasa.gov>
11
12import cPickle
13import hashlib
14import ldap
15import time
16import sys
17
18from ldap.controls import SimplePagedResultsControl
19
20from acct_mgr.api import IPasswordStore
21from trac.config import IntOption, Option, BoolOption
22from trac.core import Component, TracError, implements
23from trac.perm import IPermissionGroupProvider
24from trac.util.text import to_unicode
25from trac.util.translation import _
26
27
28GROUP_PREFIX = '@'
29NOCACHE = 0
30
31__all__ = ['DirAuthStore']
32
33
34def to_utf8(text):
35    # Account for poor behavior of to_utf8 in Trac < 1.0.2
36    if isinstance(text, unicode):
37        return text.encode('utf-8')
38    else:
39        from trac.util.text import to_utf8
40        return to_utf8(text)
41
42
43class DirAuthStore(Component):
44    """Directory Password Store for Account Manager """
45    implements(IPasswordStore, IPermissionGroupProvider)
46
47    dir_uri = Option('account-manager', 'dir_uri', 'ldap://localhost',
48                     "URI of the LDAP or Active Directory Server")
49
50    dir_charset = Option('account-manager', 'dir_charset', 'utf-8',
51                         "Text encoding used by the LDAP or Active "
52                         "Directory Server")
53
54    dir_scope = IntOption('account-manager', 'dir_scope', 2,
55                          "0=Base, 1=OneLevel, 2=Subtree")
56
57    dir_binddn = Option('account-manager', 'dir_binddn', '',
58                        "DN used to bind to AD, leave blank for "
59                        "anonymous bind")
60
61    dir_bindpw = Option('account-manager', 'dir_bindpw', '',
62                        "Password used when binding to AD, leave blank for "
63                        "anonymous bind")
64
65    dir_timeout = IntOption('account-manager', 'dir_timeout', 5,
66                            "ldap response timeout in seconds")
67
68    dir_basedn = Option('account-manager', 'dir_basedn', None,
69                        "Base DN used for account searches")
70
71    dir_pagesize = IntOption('account-manager', 'dir_pagesize', 1000,
72                             "Page size for ldap queries.")
73
74    user_attr = Option('account-manager', 'user_attr', 'sAMAccountName',
75                       "Attribute of the user in the directory")
76
77    name_attr = Option('account-manager', 'name_attr', 'displayName',
78                       "Attribute of the users name in the directory")
79
80    email_attr = Option('account-manager', 'email_attr', 'mail',
81                        "Attribute of the users email in the directory")
82
83    proxy_attr = Option('account-manager', 'proxy_attr', 'proxyAddress',
84                        "Attribute of the users proxyAddress in the directory")
85
86    member_attr = Option('account-manager', 'member_attr', 'member',
87                         "Attribute to determine members of a group")
88
89    group_class_attr = Option('account-manager', 'group_class_attr', 'group',
90                              "Attribute of the group class")
91
92    group_basedn = Option('account-manager', 'group_basedn', None,
93                          "Base DN used for group searches")
94
95    group_validusers = Option('account-manager', 'group_validusers', None,
96                              "CN of group containing valid users. If None, "
97                              "any AD user is valid")
98
99    group_knownusers = BoolOption('account-manager', 'group_knownusers', False,
100                             "Boolean: Display only the already known users.")
101
102    group_expand = IntOption('account-manager', 'group_expand', 1,
103                             "binary: expand ldap_groups into trac groups.")
104
105    group_nested = BoolOption('account-manager', 'group_nested', False,
106                             "Boolean: also add all parent groups of each group containing the user.")
107
108    group_spaces2underscore = BoolOption('account-manager', 'group_spaces2underscore', True,
109                             "Boolean:Replace spaces in group names with underscores.")
110
111    group_nameattr = Option('account-manager', 'group_nameattr', 'cn',
112                             "Specify the attribute to read the group name. Defaults to 'cn'. For full group names use 'dn'.")
113
114    cache_ttl = IntOption('account-manager', 'cache_timeout', 60,
115                          "cache timeout in seconds")
116
117    cache_memsize = IntOption('account-manager', 'cache_memsize', 400,
118                              "size of memcache in entries, zero to disable")
119
120    cache_memprune = IntOption('account-manager', 'cache_memprune', 5,
121                               "percent of entries to prune")
122
123    cache_memsize_warn = IntOption('account-manager', 'cache_memsize_warn',
124                                   300,
125                                   "warning message for cache pruning in "
126                                   "seconds")
127
128    def __init__(self, ldap=None):
129        self._ldap = ldap
130        self._cache = {}
131        reload(sys)
132        sys.setdefaultencoding('utf8')
133
134    # IPasswordStore methods
135
136    def config_key(self):
137        """Deprecated"""
138        raise NotImplementedError
139
140    def get_users(self):
141        """Grab a list of users from the session store."""
142        all_users = self._cache_get('allusers')
143        if all_users:
144            return all_users
145
146        # Cache miss
147        ldapCtx = self._bind_dir()
148        self.log.debug('get users')
149        if ldapCtx:
150            if self.group_knownusers:
151                userinfo = self.env.get_known_users()
152            elif self.group_validusers:
153                userinfo = self.expand_group_users(ldapCtx, self.group_validusers)
154            else:
155                users = self._ldap_search(ldapCtx, self.dir_basedn, ldap.SCOPE_SUBTREE,
156                                      "objectClass=person",
157                                      [to_utf8(self.user_attr),
158                                       to_utf8(self.email_attr),
159                                       to_utf8(self.proxy_attr),
160                                       to_utf8(self.name_attr)])
161                userinfo = [self._get_userinfo(u[1]) for u in users]
162        else:
163            raise TracError('Unable to bind to Active Directory')
164        self.log.debug('get users: %s', str(userinfo))
165
166        all_users = [u[0] for u in userinfo]
167        self._cache_set('allusers', all_users)
168        return all_users
169
170    def expand_group_users(self, ldapCtx, group):
171        """Given a group name, enumerate all members"""
172
173        if self.group_expand == 0:
174            return []
175
176        if group.startswith('@'):
177            group = group[1:]
178        group = "cn=%s,%s" % (group, self.group_basedn) if self.group_nameattr == 'cn' else group
179        self.log.debug("expand_group_users: search groups %s", group)
180        g = self._ldap_search(ldapCtx, to_utf8(group),
181                         ldap.SCOPE_SUBTREE if self.group_nested else ldap.SCOPE_BASE,
182                         attrlist=[to_utf8(self.member_attr)])
183        if g and self.member_attr in g[0][1]:
184            users = []
185            for m in g[0][1][str(self.member_attr)]:
186                self.log.debug("expand_group_users: group expand: %s", m)
187                try:
188                    e = self._ldap_search(ldapCtx, to_utf8(m), ldap.SCOPE_BASE)
189                    if e:
190                        if 'person' in e[0][1]['objectClass']:
191                            u = self._get_userinfo(e[0][1])
192                            self.log.debug("found user %s", u[0])
193                            users.append(u)
194                        elif str(self.group_class_attr) in e[0][1]['objectClass']:
195                            users.extend(self.expand_group_users(ldapCtx, e[0][0]))
196                        else:
197                            self.log.debug('The group member (%s) is neither a group nor a person', e[0][0])
198                    else:
199                        self.log.debug('Unable to find user listed in group: %s', str(m))
200                        self.log.debug('This is very strange and you should probably check '
201                                       'the consistency of your LDAP directory.', str(m))
202                except Exception, e:
203                    self.log.debug('expand_group_users: %s: Unable to find ldap user listed in group: %s', e, str(m))
204            return users
205        else:
206            self.log.debug('expand_group_users: Unable to find any members of the group %s', group)
207            return []
208
209    def has_user(self, user):
210        users = self.get_users()
211        if user in users:
212            return True
213        else:
214            return False
215
216    def check_password(self, user, password):
217        """Checks the password against LDAP."""
218        success = None
219        msg = "User Login: %s" % user
220
221        if not user or not password:
222            msg += " username or password can't be empty!"
223            self.log.error('check_password: %s', msg)
224            return success
225
226        user_dn = self._get_user_dn(user, NOCACHE)
227        if user_dn:
228            success = self._bind_dir(user_dn, password.encode(self.dir_charset)) or False
229            if success:
230                msg += " Password Verified"
231                success = True
232            elif success is False:
233                msg += " Password Failed"
234            self.log.error('check_password: %s', msg)
235        else:
236            msg += " does not exist, deferring authentication"
237            self.log.error('check_password: %s', msg)
238            return success
239
240        # Check the user is part of the right group, we don't use the cache
241        # Here as this is part of 'authentication' vs 'authorization'
242        if self.group_validusers:
243            usergroups = self._expand_user_groups(user, NOCACHE)
244            if self.group_validusers not in usergroups:
245                msg += " but user is not in %s : %s" % (self.group_validusers, usergroups)
246                self.log.error('check_password: %s', msg)
247                return False
248
249        # Update the session data at each login,
250        # Note the use of NoCache to force the update(s)
251        attrs = [self.user_attr, self.email_attr, self.proxy_attr, self.name_attr]
252        lfilter = '(&(%s=%s)(objectClass=person))' % (self.user_attr, user)
253        users = self._dir_search(self.dir_basedn, self.dir_scope,
254                                 lfilter, attrs, NOCACHE)
255
256        if not users:
257            raise TracError(_("Authenticated, but didn't find the user with "
258                              "filter: %(filter)s (%(users)s)",
259                              filter=filter, users=users))
260
261        # Update the session table to make this a valid user.
262        user_info = self._get_userinfo(users[0][1])
263        self._populate_user_session(user_info)
264
265        # Update the users by doing a search w/o cache
266        self.get_users()
267
268        return success
269
270    def delete_user(self, user):
271        """Can't delete from LDAP."""
272        raise NotImplementedError(_("Deleting users is not supported."))
273
274    def get_user_groups(self, user):
275        """Returns all groups for a user."""
276        return self._expand_user_groups(user)
277
278    def get_permission_groups(self, username):
279        """Return a list of names of the groups that the user with the
280        specified name is a member of."""
281        return self._expand_user_groups(username)
282
283    # Internal methods
284
285    def _bind_dir(self, user_dn=None, passwd=None):
286
287        if not self.dir_uri:
288            raise TracError(_("The dir_uri ini option must be set."))
289
290        if not self.dir_uri.lower().startswith('ldap'):
291            raise TracError(_("The dir_uri URI must start with ldap: %s", self.dir_uri))
292
293        if user_dn and passwd:
294            user_ldap = ldap.ldapobject.ReconnectLDAPObject(self.dir_uri,
295                                                            retry_max=5, retry_delay=1)
296
297            self.log.debug("_bind_dir: attempting specific bind to %s as %s",
298                           self.dir_uri, unicode(user_dn, 'utf8'))
299            try:
300                user_ldap.simple_bind_s(user_dn, passwd)
301            except Exception, e:
302                self.log.error("_bind_dir: binding failed. %s", e)
303                return None
304            return 1
305
306        # Return cached handle for default use.
307        if self._ldap:
308            return self._ldap
309
310        self._ldap = ldap.ldapobject.ReconnectLDAPObject(self.dir_uri,
311                                                         retry_max=5,
312                                                         retry_delay=1)
313
314        if self.dir_binddn:
315            self.log.debug("_bind_dir: attempting general bind to %s as %s",
316                           self.dir_uri, self.dir_binddn)
317        else:
318            self.log.debug("_bind_dir: attempting general bind to %s "
319                           "anonymously", self.dir_uri)
320
321        try:
322            self._ldap.simple_bind_s(self.dir_binddn, self.dir_bindpw)
323        except ldap.LDAPError, e:
324            raise TracError("cannot bind to %s: %s" % (self.dir_uri, e))
325
326        self.log.debug("_bind_dir: Bound to %s correctly.", self.dir_uri)
327
328        # Allow restarting.
329        self._ldap.set_option(ldap.OPT_RESTART, 1)
330        self._ldap.set_option(ldap.OPT_TIMEOUT, self.dir_timeout)
331
332        return self._ldap
333
334    # ## searches
335    def _get_user_dn(self, user, cache=1):
336        """Get users dn."""
337
338        dn = self._cache_get('dn: %s' % user)
339        if dn:
340            return dn
341
342        u = self._dir_search(self.dir_basedn, self.dir_scope,
343                             "(&(%s=%s)(objectClass=person))"
344                             % (self.user_attr, user),
345                             [self.user_attr], cache)
346
347        if not u:
348            self.log.debug("_get_user_dn: user not found: %s", user)
349            dn = None
350        else:
351            dn = u[0][0]
352            self._cache_set('dn: %s' % user, dn)
353            self.log.debug("_get_user_dn: user %s has dn: %s", user, dn)
354        return dn
355
356    def _expand_user_groups(self, user, use_cache=1):
357        """Get a list of all groups this user belongs to. This recurses up
358        to make sure we get them all.
359        """
360
361        if use_cache:
362            groups = self._cache_get('usergroups:%s' % user)
363            if groups:
364                return groups
365
366        groups = []
367        user_dn = self._get_user_dn(user)
368
369        if not user_dn:
370            self.log.debug("_expand_user_groups: username=%s has no dn.", user)
371            return []
372
373        if self.group_expand or self.group_validusers:
374            basedn = self.group_basedn or self.dir_basedn if self.group_expand else self.group_validusers[1:]
375            group_filter = ('(&(objectClass=%s)(%s=%s))') % (self.group_class_attr, self.member_attr, user_dn)
376            user_groups = self._dir_search(basedn, self.dir_scope,
377                                           group_filter, [self.group_nameattr])
378
379            for entry in user_groups:
380                groupdn = entry[0]
381                if self.group_nameattr != 'dn':
382                    group = entry[1][self.group_nameattr][0]
383                else:
384                    group = entry[0]
385                if self.group_spaces2underscore:
386                    group = group.replace(' ', '_')
387                group = '%s%s' % (GROUP_PREFIX, group)
388                if self.dir_charset == 'utf-8':
389                    group = group.decode('utf-8')
390                group = group.lower()
391                self.log.debug('_expand_user_groups: %s repr=%s', group, repr(group))
392                if group not in groups:
393                    groups.append(group)  # dn
394                    groups = self._get_parent_groups(groups, groupdn)
395            self.log.debug("_expand_user_groups: received groups: %s", groups)
396
397        if self.group_expand == 0:
398            gg = []
399            if use_cache == 0:
400                for g in groups:
401                    self.log.debug("_expand_user_groups: %s == %s", g, self.group_validusers)
402                    if g == self.group_validusers:
403                        gg.append(g)
404            groups = gg
405        elif self.group_nested and self.group_nameattr == 'dn':
406            gg = []
407            for g in groups:
408                if g.startswith(GROUP_PREFIX):
409                    g = g[1:]
410                while True:
411                    if g == self.group_basedn:
412                        break
413                    if g in gg:
414                        break
415                    gg.append(GROUP_PREFIX + g)
416                    g = g[(g.index(',') + 1):]
417            groups = gg
418
419        if use_cache:
420            self._cache_set('usergroups:%s' % user, groups)
421        if groups:
422            self.log.debug('_expand_user_groups: username=%s has groups %s', user, ', '.join(groups))
423            return sorted(groups)
424        else:
425            self.log.debug('_expand_user_groups: username=%s has no groups.', user)
426            return []
427
428
429    def _get_parent_groups(self, groups, group_dn):
430        group_filter = '(&(objectClass=%s)(%s=%s))' % (self.group_class_attr, self.member_attr, group_dn)
431        basedn = self.group_basedn or self.dir_basedn
432        ldap_groups = self._dir_search(basedn, self.dir_scope,
433                                       group_filter, [self.group_nameattr])
434        if ldap_groups:
435            for entry in ldap_groups:
436                groupdn = entry[0]
437                if self.group_nameattr != 'dn':
438                    group = entry[1][self.group_nameattr][0]
439                else:
440                    group = entry[0]
441                if self.group_spaces2underscore:
442                    group = group.replace(' ', '_')
443                group = group.lower()
444                if group not in groups:
445                    self.log.debug('_get_parent_groups: adding group %s' % group)
446                    groups.append(group)
447                    groups = self._get_parent_groups(groups, groupdn)
448        return groups
449
450    def _get_userinfo(self, attrs):
451        """Extract the userinfo tuple from the LDAP search result."""
452        user_name = attrs[self.user_attr][0].lower()
453        display_name = attrs.get(self.name_attr, [''])[0]
454        email = ''
455        if self.email_attr in attrs:
456            email = attrs[self.email_attr][0].lower()
457            self.log.debug("user %s has email %s", user_name, email)
458        elif 'proxyAddresses' in attrs:
459            for e in attrs['proxyAddresses']:
460                if e.startswith('SMTP:'):
461                    email = e[5:]
462                continue
463        return user_name, display_name, email
464
465    def _populate_user_session(self, userinfo):
466        """Create user session entries and populate email and last visit."""
467
468        # Kind of ugly.  First try to insert a new session record.  If it
469        # fails, don't worry, means it's already there.  Second, insert the
470        # email address session attribute.  If it fails, don't worry, it's
471        # already there.
472        uname, displayname, email = userinfo
473
474        with self._get_db() as db:
475
476            cur = db.cursor()
477            try:
478                cur.execute("""
479                    DELETE FROM session
480                      WHERE sid=%s AND authenticated=1
481                    """, (uname,))
482                cur.execute("""
483                    INSERT INTO session
484                      (sid, authenticated, last_visit)
485                    VALUES (%s, 1, %s)""", (uname, 0))
486            except:
487                self.log.debug("_populate_user_session: Session for %s exists.", uname)
488
489            # Assume enabled if we get this far self.env.get_known_users()
490            # needs this..
491            # TODO need to have it updated by the get_dn stuff long term so the
492            # db matches the auth source.
493            cur = db.cursor()
494            try:
495                cur.execute("""
496                    DELETE FROM session_attribute
497                      WHERE sid=%s AND authenticated=1 AND name='enabled'
498                    """, (uname,))
499                cur.execute("""
500                    INSERT INTO session_attribute
501                      (sid, authenticated, name, value)
502                    VALUES (%s, 1, 'enabled', '1')
503                    """, (uname,))
504            except:
505                self.log.debug("_populate_user_session: Session for %s exists.", uname)
506
507            if email:
508                cur = db.cursor()
509                cur.execute("""
510                    DELETE FROM session_attribute
511                      WHERE sid=%s AND authenticated=1 AND name='email'
512                    """, (uname,))
513                cur.execute("""
514                    INSERT INTO session_attribute
515                      (sid, authenticated, name, value)
516                    VALUES (%s, 1, 'email', %s)
517                    """, (uname, to_unicode(email)))
518                self.log.debug("_populate_user_session: updating user session email info for %s (%s)",
519                              uname, to_unicode(email))
520
521            if displayname:
522                cur = db.cursor()
523                cur.execute("""
524                    DELETE FROM session_attribute
525                      WHERE sid=%s AND authenticated=1 AND name='name'
526                    """, (uname,))
527                cur.execute("""
528                    INSERT INTO session_attribute
529                      (sid, authenticated, name, value)
530                    VALUES (%s, 1, 'name', %s)
531                    """, (uname, to_unicode(displayname)))
532                self.log.debug("_populate_user_session: updating user session displayname info for %s (%s)",
533                              uname, to_unicode(displayname))
534
535            return self._close_db(db)
536
537    def _cache_get(self, key=None, ttl=None):
538        """Get an item from memory cache"""
539        cache_ttl = ttl or self.cache_ttl
540        if not self.cache_memsize:
541            return None
542
543        now = time.time()
544
545        if key in self._cache:
546            lut, data = self._cache[key]
547            if lut + cache_ttl >= now:
548                self.log.debug("_cache_get: memcache hit for %s", key)
549                return data
550            else:
551                del self._cache[key]
552        return None
553
554    def _cache_set(self, key=None, data=None, cache_time=None):
555        if not self.cache_memsize:
556            return None
557        now = time.time()
558        if not cache_time:
559            cache_time = now
560
561        # Prune if we need to.
562        if len(self._cache) > self.cache_memsize:
563            # Warn if too frequent.
564            if 'last_prune' in self._cache:
565                last_prune, data = self._cache['last_prune']
566                if last_prune + self.cache_memsize_warn > now:
567                    self.log.info("pruning memcache in less than %d seconds, "
568                                  "you might increase cache_memsize.",
569                                  self.cache_memsize_warn)
570
571            self.log.debug("_cache_set: pruning memcache by %d: (current: %d > max: %d )",
572                           self.cache_memprune, len(self._cache),
573                           self.cache_memsize)
574            cache_keys = self._cache.keys()
575            cache_keys.sort(lambda x, y: cmp(self._cache[x][0],
576                                             self._cache[y][0]))
577            # Discards the 10% oldest.
578            upper = self.cache_memprune * self.cache_memsize / 100
579            old_keys = cache_keys[:upper]
580            for k in old_keys:
581                del self._cache[k]
582                self._cache['last_prune'] = [now, []]
583
584        self._cache[key] = [cache_time, data]
585        return data
586
587    def _dir_search(self, basedn, scope, lfilter, attrs=None, check_cache=1):
588        current_time = time.time()
589
590        attrs = self._decode_list(attrs or [])
591
592        if not basedn:
593            raise TracError(_("basedn not defined!"))
594        if not lfilter:
595            raise TracError(_("filter not defined!"))
596
597        basedn = basedn
598        lfilter = lfilter
599
600        # Create unique key from the filter and the attributes.
601        keystr = to_utf8(','.join([basedn, str(scope), lfilter, ':'.join(attrs)]))
602        key = hashlib.md5(keystr).hexdigest()
603        self.log.debug("_dir_search: searching %s for %s(%s)",
604                       basedn, lfilter, key)
605
606        with self._get_db() as db:
607            # Check mem cache.
608            if check_cache:
609                ret = self._cache_get(key)
610                if ret:
611                    return ret
612
613                # --  Check database
614                cur = db.cursor()
615                cur.execute("""
616                    SELECT lut,data FROM dir_cache WHERE id=%s
617                    """, (key,))
618                row = cur.fetchone()
619                if row:
620                    lut, data = row
621
622                    if current_time < lut + self.cache_ttl:
623                        self.log.debug("_dir_search: dbcache hit for %s", lfilter)
624                        ret = cPickle.loads(str(data))
625                        self._cache_set(key, ret, lut)
626                        return ret
627                else:
628                    # Old data, delete it and anything else that's old.
629                    lut = current_time - self.cache_ttl
630                    cur.execute("""
631                        DELETE FROM dir_cache WHERE lut < %s
632                        """, (lut,))
633#                    db.commit()
634            else:
635                self.log.debug("_dir_search: skipping cache.")
636
637            ldapCtx = self._bind_dir()
638            self.log.debug("_dir_search: starting LDAP search of %s %s using %s "
639                           "for %s", self.dir_uri, basedn, lfilter, attrs)
640
641            res = []
642            try:
643                try:
644                    res = self._ldap_search(ldapCtx, basedn.encode(self.dir_charset), scope,
645                                 lfilter, attrs)
646                except ldap.LDAPError, e:
647                    # second try - does not work properly without
648                    self.log.info("got %s - doing one additional retry", e)
649                    self._ldap = None
650                    ldapCtx = self._bind_dir()
651                    res = self._ldap_search(ldapCtx, basedn.encode(self.dir_charset), scope,
652                                 lfilter, attrs)
653            except ldap.LDAPError, e:
654                self.log.error("_dir_search: Error searching %s using %s: %s",
655                               basedn, lfilter, e)
656
657            if res:
658                self.log.debug("_dir_search: dir hit, %d entries.", len(res))
659            else:
660                self.log.debug("_dir_search: dir miss.")
661
662            if not check_cache:
663                return res
664
665            # Set the db cache for the next search, even if results are empty.
666            res_str = cPickle.dumps(res, 0)
667            try:
668                cur = db.cursor()
669                cur.execute("""
670                    DELETE FROM dir_cache WHERE id=%s
671                    """, (key,))
672                self.log.debug("_dir_search: INSERT VALUES (%s, %s, %s)"
673                               % (key, current_time, buffer(res_str)))
674                cur.execute("""
675                    INSERT INTO dir_cache (id, lut, data)
676                    VALUES (%s, %s, %s)
677                    """, (key, current_time, buffer(res_str)))
678#                db.commit()
679            except Exception, e:
680                db.rollback()
681                self.log.warn("_dir_search: db cache update failed. %s" % e)
682
683            self._close_db(db)
684
685            self._cache_set(key, res)
686
687        self.log.debug("_dir_search: res=%s" % res)
688        return res
689
690    # helper method for UserExtensiblePermissionStore
691    def get_all_groups(self):
692        """Get all groups. Returns an array containing arrays [dn, cn]
693        """
694
695        if self.group_expand == 0:
696            return []
697
698        basedn = self.group_basedn or self.dir_basedn
699        group_filter = ('(objectClass=%s)') % self.group_class_attr
700        all_groups = self._dir_search(basedn, self.dir_scope, group_filter, [self.group_nameattr])
701
702        if self.group_spaces2underscore:
703            self.log.debug("get_all_groups: all=%s" % all_groups)
704            for index, item in enumerate(all_groups):
705                all_groups[index] = (item[0].replace(' ', '_'), item[1])
706
707        all_groups
708
709        self.log.debug("get_all_groups: all=%s" % all_groups)
710        return all_groups
711
712    def get_group_users(self, groupdn):
713        """Grab a list of users from the session store."""
714
715        lcnx = self._bind_dir()
716        self.log.debug('get users:')
717        if lcnx:
718                userinfo = self.expand_group_users(lcnx, groupdn)
719        else:
720            raise TracError('Unable to bind to Active Directory')
721        self.log.debug('get users: %s', str(userinfo))
722        return [u[0] for u in userinfo]
723
724    @staticmethod
725    def _decode_list(l=None):
726        newlist = []
727        if not l:
728            return l
729        for val in l:
730            #newlist.append(to_utf8(val))
731            newlist.append(val.encode('ascii', 'ignore'))
732        return newlist
733
734    def _get_db(self):
735        """ Obtain a writeable db connection """
736        if "get_db_cnx" in self.env:
737            return self.env.get_db_cnx()
738        else:
739            dbx = self.env.db_transaction
740            return dbx
741
742    def _close_db(self, db):
743        """ close the database connection. """
744        if "get_db_cnx" in self.env:
745            db.commit()
746            return db.close()
747
748        return True
749
750
751    def _ldap_search(self, context, base, scope, filterstr = '(objectClass=*)', attrlist = None):
752        """Perform a LDAP search."""
753
754        sz = int(self.dir_pagesize)
755        if sz > 0:
756            self.log.debug("_ldap_search: ldap query with page size %s", sz)
757
758        lc = SimplePagedResultsControl(True, sz, '') if sz > 0 else None
759
760        r = []
761
762        while True:
763            msgid = context.search_ext(base, scope, filterstr, attrlist, 0, [lc], None, -1, sz)
764
765            resp_type, resp_data, resp_msgid, decoded_resp_ctrls = context.result3(msgid)
766
767            r += resp_data
768
769            self.log.debug("_ldap_search: serverControls: %s", decoded_resp_ctrls)
770
771            pctrls = [
772                c
773                for c in decoded_resp_ctrls
774                if c.controlType == ldap.CONTROL_PAGEDRESULTS
775            ]
776            if pctrls:
777                cookie = pctrls[0].cookie
778                if cookie:
779                    self.log.debug("_ldap_search: cookie: %s", cookie)
780                    lc = SimplePagedResultsControl(True, sz, cookie)
781                else:
782                    break
783            else:
784                break
785
786        self.log.debug("_ldap_search: result = %s", r)
787        return r
788
Note: See TracBrowser for help on using the repository browser.