Commits

Anonymous committed d1f3eb0

First commit for the ldaplib subtree.

Comments (0)

Files changed (5)

ldaplib/ldap/__init__.py

+## ldap/__init__.py - python bindings to LDAP
+## Copyright (C) 2000  Federico Di Gregorio <fog@debian.org>
+## Copyright (C) 2000  MIXAD LIVE [http://www.mixadlive.com]
+##
+##   This program is free software; you can redistribute it and/or modify
+##   it under the terms of the GNU General Public License as published by
+##   the Free Software Foundation; either version 2 of the License, or
+##   (at your option) any later version.
+##
+##   This program is distributed in the hope that it will be useful,
+##   but WITHOUT ANY WARRANTY; without even the implied warranty of
+##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+##   GNU General Public License for more details.
+##
+##   You should have received a copy of the GNU General Public License
+##   along with this program; if not, write to the Free Software
+##   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+## -*- Mode: python -*-
+
+from _ldap import *
+
+__all__ = ['connection', 'entry', 'widgets', 'parse']
+
+DNS = ', '
+
+
+    

ldaplib/ldap/connection.py

+## ldap/connection.py - python bindings to LDAP
+## Copyright (C) 2000  Federico Di Gregorio <fog@debian.org>
+## Copyright (C) 2000  MIXAD LIVE [http://www.mixadlive.com]
+##
+##   This program is free software; you can redistribute it and/or modify
+##   it under the terms of the GNU General Public License as published by
+##   the Free Software Foundation; either version 2 of the License, or
+##   (at your option) any later version.
+##
+##   This program is distributed in the hope that it will be useful,
+##   but WITHOUT ANY WARRANTY; without even the implied warranty of
+##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+##   GNU General Public License for more details.
+##
+##   You should have received a copy of the GNU General Public License
+##   along with this program; if not, write to the Free Software
+##   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+## -*- Mode: python -*-
+
+import string, ldap, entry
+from ldap import LDAPError
+
+
+class LDAPConnection:
+    """High level wrapper around the _ldapmodule functions.
+
+       A new instance of this class binds to the LDAP server with the given
+       dn and password. The estabilished connection can be used to retrieve
+       information from the server by searching or browsing. LDAP objects
+       are returned as instances of the LDAPEntry class."""
+
+    def __init__(self, host, port=ldap.PORT,
+                 binding_dn='', auth_token='', auth_type=ldap.AUTH_SIMPLE):
+        """Open the connection and bind to the directory."""
+        self.host = host
+        self.port = port
+        self.base = binding_dn
+        self.use_rdn = 0
+        self.timeout = 60
+        self.filter = 'cn=*'
+        self.attrl = None
+        self.__connection = ldap.open(host, port)
+        self.__connection.bind_s(binding_dn, auth_token, auth_type)
+
+    def __check_base(self, base_dn):
+        """Check search base and raise exception if not set.
+        """
+        if (self.base == None or self.base == '') and base_dn == None:
+            raise LDAPError, ({'desc':'base not set'})
+        elif base_dn == None:
+            base_dn = self.base
+        elif self.use_rdn == 1:
+            base_dn = string.join([base_dn, self.base], ',')
+        return string.replace(base_dn, ' ', '')
+    
+    def search_s(self, base_dn=None, filter=None,
+                 scope=ldap.SCOPE_SUBTREE, attrl=None):
+        """Search the directory, return an array of LDAPEntry instances."""
+        base_dn = self.__check_base(base_dn)
+        if filter == None: filter = self.filter
+        if attrl == None: attrl = self.attrl
+        data = self.__connection.search_s(base_dn, scope, filter, attrl, 0,
+                                          self.timeout)
+        array = []
+        if data == None: return array 
+        for edata in data:
+            e = entry.LDAPEntry(edata[0], edata[1])
+            e.set_connection(self)
+            array.append(e)
+        return array
+
+    def find_s(self, base_dn=None, attrl=None):
+        """Search for base_dn, return None if not found."""
+        filter = '(!(dn=*))'
+        try:
+            root = self.search_s(base_dn, filter, ldap.SCOPE_BASE, attrl)   
+        except LDAPError:
+            root = []
+        if len(root) == 1: return root[0]
+        else: return None
+        
+    def root_s(self, base_dn=None, attrl=None):
+        """Return the root object using base_dn as the base for the search.
+
+           Note the strange search rule! It guarantee to return the entry,
+           even if it does not have attributes...
+        """
+        filter = '(!(dn=*))'
+        root = self.search_s(base_dn, filter, ldap.SCOPE_BASE, attrl)
+        if len(root) == 0:
+            raise LDAPError, ({'desc':'dn does not exists'},)
+        return root[0]
+
+    def browse_s(self, base_dn=None, filter=None, attrl=None):
+        """Return the objects one level under base_dn, sorted by filter."""
+        return self.search_s(base_dn, filter, ldap.SCOPE_ONELEVEL, attrl)
+        
+    def modify_s(self, dn, modlist):
+        """Modify dn using the given modlist."""
+        self.__connection.modify_s(self.__check_base(dn), modlist)
+
+    def add_s(self, dn, modlist):
+        """Add to dn the given list of attributes."""
+        self.__connection.add_s(self.__check_base(dn), modlist)
+
+    def delete_s(self, dn):
+        """Delete the given dn."""
+        self.__connection.delete_s(self.__check_base(dn))
+
+    def rebind(binding_dn='', auth_token='', auth_type=ldap.AUTH_SIMPLE):
+        """Rebind to the directory changing authorization info."""
+        self.__connection.unbind_s()
+        self.__connection.bind_s(binding_dn, auth_token, auth_type)

ldaplib/ldap/entry.py

+## ldap/entry.py - python bindings to LDAP
+## Copyright (C) 2000  Federico Di Gregorio <fog@debian.org>
+## Copyright (C) 2000  MIXAD LIVE [http://www.mixadlive.com]
+##
+##   This program is free software; you can redistribute it and/or modify
+##   it under the terms of the GNU General Public License as published by
+##   the Free Software Foundation; either version 2 of the License, or
+##   (at your option) any later version.
+##
+##   This program is distributed in the hope that it will be useful,
+##   but WITHOUT ANY WARRANTY; without even the implied warranty of
+##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+##   GNU General Public License for more details.
+##
+##   You should have received a copy of the GNU General Public License
+##   along with this program; if not, write to the Free Software
+##   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+## -*- Mode: python -*-
+
+import string, re, ldap
+from ldap import LDAPError
+
+
+class LDAPEntry:
+    """Wrapper around LDAP objects."""
+
+    def __init__(self, dn, attrs={}):
+        """Initialize from a dictionary of attributes."""
+        self.dn = dn
+        self.rdn = dn
+        self.childrens= {}
+        self.ancestor = None
+        self.data = {}
+        self._connection = None
+        self.__list_type_cache = type([])
+        for k in attrs.keys():
+            LDAPEntry.__setitem__(self, k, attrs[k], 0)
+
+    # some methods used to make the class work as a dictionary
+    def __setitem__(self, key, value, changed=1):
+        if key == 'dn':
+            self.dn = dn
+            if self.ancestor != None: self.set_ancestor(self.ancestor)
+        else:
+            if type(value) != self.__list_type_cache: value = [value]
+            self.data[key] = (value, changed)
+    def __getitem__(self, key):
+        if key == 'dn': return self.dn
+        elif self.data[key][1] != 2: return self.data[key][0]
+    def __delitem__(self, key):
+        if key == 'dn': raise LDAPError, ({'desc':'cannot delete dn'})
+        else: self.data[key] = (self.data[key][0], 2)
+    def __purge__(self):
+        for k in self.data.keys():
+            if self.data[k][1] == 2: del self.data[k]
+    def __len__(self):
+        return len(self.data)+1
+    def keys(self):
+        return self.data.keys()
+    def has_key(self, key):
+        return self.data.has_key(key)
+    def values(self):
+        l = []
+        for k in self.data.keys():
+            l.append(self.data[k][0])
+        return l
+    def items(self):
+        l = []
+        for k in self.data.keys():
+            l.append((k, self.data[k][0]))
+        return l
+
+    # basic operations on *single* attribute values
+    def get(self, key, default=None):
+        return self.data.get(key, (default,))[0]
+    def set(self, key, value):
+        if self.data.has_key(key) and self.data[key][1] < 2:
+            self.data[key] = (self.data[key][0]+[value], 1)
+        else:
+            self.data[key] = ([value], 1)
+    def replace(self, key, oldvalue, newvalue):
+        i = self.data[key][0].index(oldvalue)
+        self.data[key][0][i:i+1] = [newvalue]
+        self.data[key] = (self.data[key][0], 1)
+    def delete(self, key, value):
+        self.data[key][0].remove(value)
+        self.data[key] = (self.data[key][0], 1)
+            
+    def __str__(self):
+        """Return the ldif rapresentation of this object."""
+        str = "dn: %s\n" % self.dn
+        classes = self.get('objectClass', []) + self.get('objectclass', []) 
+        for oc in classes:
+            str = str + "objectClass: %s\n" % oc
+        for k in self.data.keys():
+            if re.match('object[cC]lass', k): continue
+            for a in self.data[k][0]:
+                str = str + "%s: %s\n" % (k, a)
+        return str
+    
+    def __check_connection(self):
+        """Raise an exception if the connection to the directory is missing."""
+        if self._connection == None:
+            raise LDAPError, ({'desc':'missing connection'})
+
+    def set_connection(self, connection):
+        """Set the connection this object should use for directory access."""
+        self._connection = connection
+        
+    def set_ancestor(self, ancestor):
+        """Set the ancestor and calculate rdn."""
+        self.ancestor = ancestor
+        parts = ldap.explode_dn(self.dn)
+        aparts = ldap.explode_dn(ancestor.dn)
+        rdnparts = []
+        for p in parts:
+            if len(aparts) > 0 and p == aparts[0]: del aparts[0]
+            else: rdnparts.append(p)
+        self.rdn = string.join(rdnparts, ldap.DNS)
+
+    def init(self, dn, attrl=None):
+        """Initialize itself from given dn."""
+        self.__check_connection()
+        c = self._connection
+        new = c.search_s(self.dn, '(!(dn=*))', ldap.SCOPE_BASE, attrl)
+        if len(new) == 0:
+            raise LDAPError, ({'desc':'can not find dn'})
+        items = {}
+        for item in new[0].items():
+            items[item[0]] = item[1]
+        LDAPEntry.__init__(self, dn, items)
+        self.set_connection(c)
+        
+    def browse(self, filter=None, attrl=None):
+        """Retrieve childrens from the database.
+
+           The directory level under the current object is browse and filtered
+           by filter. Every object found is then added to the childrens hash
+           using its rdn as the key. childrens is the returned for commodity.
+        """
+        self.__check_connection()
+        childs = self._connection.browse_s(self.dn, filter, attrl)
+        self.childrens = {}
+        for child in childs:
+            child.set_ancestor(self)
+            self.childrens[child.rdn] = child
+        return self.childrens
+
+    def recurse_pre(self, func, *args):
+        """Apply func() recursively to the whole children tree."""
+        for ck in self.childrens.keys():
+            apply(LDAPEntry.recurse_pre, (self.childrens[ck], func) + args)
+        apply(func, (self,) + args)
+
+    def recurse_post(self, func, *args):
+        """Apply func() recursively to the whole children tree."""
+        apply(func, (self,) + args)
+        for ck in self.childrens.keys():
+            apply(LDAPEntry.recurse_post, (self.childrens[ck], func) + args)
+
+    def commit(self, new_dn=None, force=0):
+        """Commit (save) the entry to the LDAP directory.
+
+           First a query is done to obtain the attribute names, then do
+           an ADD for every new attribute and a MODIFY for every modified
+           one. Attributes not found in this object are deleted!
+        """
+        old = self._connection.find_s(self.dn)
+            
+        # now that we have the attributes, build the modify array
+        mod = []
+        if old == None:
+            for k in self.keys():
+                mod.append((k, self.data[k][0]))
+            self._connection.add_s(self.dn, mod)
+        else:
+            for k in self.keys():
+                if old.has_key(k):
+                    if self.data[k][1] == 2:    # delete attribute from server
+                        mod.append((ldap.MOD_DELETE, k, None))
+                    elif self.data[k][1] == 1:  # modify existing attribute
+                        mod.append((ldap.MOD_REPLACE, k, self.data[k][0]))
+                    else:
+                        if self.data[k][1] == 1:
+                            mod.append((ldap.MOD_ADD, k, self.data[k][0]))
+            self._connection.modify_s(self.dn, mod)
+        self.__purge__()
+
+
+
+
+
+
+
+

ldaplib/ldap/parse.py

+## ldap/parse.py - parsing of LDAP objectClasses and attribute maps 
+## Copyright (C) 2000  Federico Di Gregorio <fog@debian.org>
+## Copyright (C) 2000  MIXAD LIVE [http://www.mixadlive.com]
+##
+##   This program is free software; you can redistribute it and/or modify
+##   it under the terms of the GNU General Public License as published by
+##   the Free Software Foundation; either version 2 of the License, or
+##   (at your option) any later version.
+##
+##   This program is distributed in the hope that it will be useful,
+##   but WITHOUT ANY WARRANTY; without even the implied warranty of
+##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+##   GNU General Public License for more details.
+##
+##   You should have received a copy of the GNU General Public License
+##   along with this program; if not, write to the Free Software
+##   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+## -*- Mode: python -*-
+
+
+import string, re
+from ldap import LDAPError
+
+
+class FileReader:
+    """Attach to an open file and provide iteration functions."""
+    def __init__(self, file):
+        self.__file = file
+        self.line = ''
+    def readline(self):
+        self.line = self.__file.readline()
+        if self.line == '': return 0
+        else: return 1
+        
+        
+class LDAPObjectClass:
+    """Wrapper around LDAP objectClass definitions.
+
+       Every instance of this class describe a single objectClass antry and
+       can be used to validate LDAPEntry objects. Note that the file parameter
+       is *not* file name, but an open and ready to read file object.
+    """
+    def __init__(self, file=None):
+        self.name = ''
+        self.must = []
+        self.may = []
+        if file:
+            self.load(file, 0)
+            if self.name == '':
+                raise LDAPError, "not enought data"
+        
+    def load(self, file, init=1):
+        """Scan objectClass definition file and init.
+
+           Return 0 if EOF is encountered while scanning the file for data.
+        """
+        if not init: self.__init__()
+        n = None; d = None
+        i = FileReader(file)
+        while i.readline():
+            ll = len(i.line)
+            l = string.strip(i.line[0:-1])
+            moc = re.match('object[cC]lass\s+(.+)', l)
+            if n == None:
+                if moc == None: continue
+                else:
+                    n = moc.group(1)
+                    self.name = n
+                    moc = None
+            else:
+                if moc != None:
+                    # rewind the file to let it ready for the next one
+                    file.seek(-ll, 1)
+                    return 1
+                else:
+                    if len(l) <= 1: continue
+                    elif re.match('requires', l):
+                        d = self.must
+                    elif re.match('allows', l):
+                        d = self.may
+                    elif d != None:
+                        val = string.replace(l, ',', '')
+                        d.append(val)
+            if l == '': return 0
+            else: return 1
+
+    def save(self, file):
+        """Save a human-readable version of the objectClass."""
+        file.write('objectClass %s\n' % self.name)
+        file.write('    requires\n')
+        for i in range(len(self.must)):
+            if i != 0: file.write(',\n')
+            file.write('        '+self.must[i])
+        file.write('\n    allows\n')
+        for i in range(len(self.may)):
+            if i != 0: file.write(',\n')
+            file.write('        '+self.may[i])
+        file.write('\n\n')
+
+
+def load_objectclasses(*file_names):
+    """Load all the objectClasses from file_names and return them in array."""
+    all = []
+    for fn in file_names:
+        f = open(fn)
+        try:
+            oc = LDAPObjectClass(f)
+            while oc:
+                all.append(oc)
+                oc = LDAPObjectClass(f)
+        except LDAPError:
+            f.close()
+    return all
+
+
+def load_objectclasses_d(*file_names):
+    """Load all the objectClasses from file_names and return a dictionary."""
+    dict = {}
+    for fn in file_names:
+        ar = load_objectclasses(fn)
+        for i in range(len(ar)):
+            dict[ar[i].name] = ar[i]
+    return dict
+
+
+# TODO: implement me!
+def sort_attrs_by_oc(attrs, *ocs):
+    return attrs
+
+
+def __load_attributes(file_name):
+    f = open(file_name, 'r')
+    i = FileReader(f)
+    attrs = []
+    while i.readline():
+        parts = string.split(i.line)
+        if parts == None or len(parts) < 2 or parts[0][0] == '#':
+            continue
+        else:
+            if len(parts) > 2: aliases = parts[1:-1]
+            else: aliases = []
+            attrs.append((parts[0], parts[-1], aliases))
+    close(f)
+    return attrs
+
+def load_attributes(*file_names):
+    """Load all the attributes from file_names and return them in tuples.
+
+       The format of every tuple (attribute) is as follows:
+       (attribute_name, attribute_type, attribute_aliases)
+    """
+    all = []
+    for fn in file_names:
+        all = all + __load_attributes(fn)
+    return all
+
+
+def canonical_dn(*parts):
+    """Return canonical dn from parts."""
+    return string.strip(string.replace(string.join(parts, ','), ', ', ','))

ldaplib/ldap/widgets.py

+## ldap/widgets.py - GTK widgets for LDAP
+## Copyright (C) 2000  Federico Di Gregorio <fog@debian.org>
+## Copyright (C) 2000  MIXAD LIVE [http://www.mixadlive.com]
+##
+##   This program is free software; you can redistribute it and/or modify
+##   it under the terms of the GNU General Public License as published by
+##   the Free Software Foundation; either version 2 of the License, or
+##   (at your option) any later version.
+##
+##   This program is distributed in the hope that it will be useful,
+##   but WITHOUT ANY WARRANTY; without even the implied warranty of
+##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+##   GNU General Public License for more details.
+##
+##   You should have received a copy of the GNU General Public License
+##   along with this program; if not, write to the Free Software
+##   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+## -*- Mode: python -*-
+
+import sys, re, string, ldap, _gtk
+import connection, entry
+from gtk import *
+from ldap import LDAPError
+
+GtkLDAPError = 'GtkLDAPError'
+
+
+##################################################the GtkLDAPClassEntry ########
+
+class GtkLDAPClassEntry(GtkOptionMenu):
+    """Simple widget to select an objectClass from a list.
+
+       This widget can automagically update a GtkLDAPAttributeEntry widget
+       every time a new class is selected, just set the attribute entry with
+       the set_attribute_entry method.
+    """
+    def __init__(self, ocd):
+        GtkOptionMenu.__init__(self)
+        self.ocd = ocd
+        self.menu = GtkMenu()
+        self.menu.set_title('objectClass entry')
+        self.menu.show()
+        for k in ocd.keys():
+            child = GtkMenuItem(k); child.show()
+            child.connect('activate-item', self.__activate_item, self)
+            self.menu.append(child)
+        self.set_menu(menu)
+        self.attr_entry = None
+
+    def __activate_item(child, self):
+        print child, self
+ 
+    def set_attribute_entry(self, entry):
+        self.attr_entry = entry
+        self.attr_entry.update()
+
+    
+        
+
+
+################################################ the GtkLDAPEntry class ########
+
+## class GtkLDAPEntry(GtkVBox):
+##     "Rapresents a new LDAP directory entry, lets the user modify it."
+
+##     def __init__(self, oclist):
+##         GtkVBox.__init__(self)
+##         self.oclist = oclist
+
+##         table = GtkTable(2, 2, 0)
+##         table.set_col_spacings(4)
+##         table.set_row_spacings(4)
+##         table.set_border_width(4)
+##         table.show()
+        
+##         # build the objectClass selector
+##         l = GtkLabel('objectClass'); l.show()
+##         menu = GtkMenu()
+##         menu.set_title('objectClass selector')
+##         menu.show()
+##         for k in oclist.keys():
+##             child = GtkMenuItem(k); child.show()
+##             menu.append(child)
+##         self.ocselector = GtkOptionMenu()
+##         self.ocselector.set_menu(menu)
+##         self.ocselector.show()
+##         table.attach(l, 0,1, 0,1, xoptions=FILL, yoptions=FILL)
+##         table.attach(self.ocselector, 1,2, 0,1,
+##                      xoptions=FILL+EXPAND, yoptions=FILL)
+
+##         # build the attribute selector
+##         l = GtkLabel('attribute'); l.show()
+##         child = GtkMenuItem(""); child.show()
+##         menu.append(child)
+##         self.attrselector = GtkOptionMenu()
+##         self.attrselector.set_menu(menu)
+##         self.attrselector.show()
+##         table.attach(l, 0,1, 1,2, xoptions=FILL, yoptions=FILL)
+##         table.attach(self.attrselector, 1,2, 1,2,
+##                      xoptions=FILL+EXPAND, yoptions=FILL)
+        
+##         # build the clist with the attribute values
+##         self.attrlist = GtkCList(2, ['Attribute', 'Value'])
+##         ctree.set_column_auto_resize(0, TRUE)
+##         ctree.set_column_auto_resize(1, TRUE)
+##         self.attrlist.show()
+
+##         hbox = GtkHBox()
+##         hbox.show()
+##         hbox.pack_start(self.attrlist)
+##         hbox.pack_start(table)
+##         self.pack_start(hbox)
+
+##     def set_data(self, data):
+##         """Initialize the widget with data taken from the data dictionary.
+##            The data dictionary entries keys are the LDAP attribute names,
+##            the values are lists of attribute values (LDAP supports multiple
+##            attributes with the same name.)"""
+        
+##         # clear the clist and the attribute menu (i.e., create a new menu)
+##         self.attrlist.freeze(); self.attrlist.clear()
+##         menu = GtkMenu(); menu.show()
+##         menu.set_title('attribute selector')
+##         self.attrselector.set_menu(menu)
+        
+##         # locate the distinguished name and insert it
+##         v = data['dn']
+##         self.attrlist.insert(0, ['dn', v])
+
+##         # locate and insert the objectClasses (and builds the menu used in
+##         # the attribute selector, much simplier to generate it here)
+##         row = 1
+##         a = data['objectClass']
+##         for v in a:
+##             self.attrlist.insert(row, ['objectClass', v])
+##             row = row + 1
+##             #for attr in self.oclist[v]
+
+##         # insert all the other attributes 
+##         for k in data.keys():
+##             if k == 'dn' or k == 'objectClass': continue
+##             for v in data[k]:
+##                 self.attrlist.insert(row, [k, v])
+##                 row = row + 1
+        
+        
+        
+
+######################################## the GtkLDAPDirectoryTree class ########
+
+class GtkLDAPDirectoryTree(GtkCTree):
+    """Rapresents an LDAP directory tree as a GtkCTree."""
+
+    def __init__(self, connection, titles=None):
+        if titles == None: titles = ['Info', 'Node', 'Value']
+        self.__connection = connection
+        self.__cols = len(titles)
+        GtkCTree.__init__(self, self.__cols, 1, titles)
+
+        self.base = connection.base
+        self.use_rdn = 1
+        self.search_default = '(!(dn=*))'
+        self.color_dn = self.get_colormap().alloc('black')
+        self.color_data = self.get_colormap().alloc('blue')
+        self.color_data_modified = self.get_colormap().alloc('red')
+        self.roots = []
+        
+        self.set_indent(8)
+        self.set_column_auto_resize(1, TRUE)
+        self.set_column_visibility(0, FALSE)
+
+        self.expand_id = self.connect('tree-expand', self.__expand)
+        self.collapse_id = self.connect('tree-collapse', self.__collapse)
+        
+        if self.base != None: self.set_browse_base(self.base)
+
+    def __fill(self, node, entry):
+        """Fill a node with information gathered from the LDAPEntry."""
+        for key in entry.keys():
+            for s in entry[key]:
+                n = self.insert_node(node, None, [key, key, s], 0,
+                                     None, None, None, None, FALSE, TRUE)
+                self.node_set_foreground(n, self.color_data)
+                self.node_set_row_data(n, entry)       
+        
+    def __expand(self, tree, node):
+        """Takes a GtkCTree and a node rapresenting a distinguished name and
+           add to the tree the nodes rapresenting a one level browse and the
+           leafs rapresenting all the attributes of the node.
+        """
+        base = tree.node_get_row_data(node)
+        if base.__gtk_expanded != 0: return
+        
+        # grab dn from node text and search LDAP directory (but only
+        # if the data has not already been gathered)
+        if len(base.childrens) == 0: childs = base.browse(self.search_default)
+        else: return
+
+        # now add a non-leaf node for every children
+        tree.freeze()
+        for child in childs.values():
+            if self.use_rdn == 1: rdn = child.rdn
+            else: rdn = child.dn
+            cn = tree.insert_node(node, None, ['dn', rdn, ''], 0,
+                                       None, None, None, None, FALSE, FALSE)
+            self.__fill(cn, child)
+            child.__gtk_expanded = 0
+            tree.node_set_row_data(cn, child)
+        tree.thaw()
+        base.__gtk_expanded = 1
+        
+    def __collapse(self, tree, node):
+        """Collapse a tree node by removing all the childrens and replacing
+           them all by a single fake node.
+        """
+        print "debug: tree collapsed on node:", node
+        base = tree.node_get_row_data(node)
+        base.__gtk_expanded = 0
+        
+    def __check_base(self, dn):
+        """Check self.base and dn and raise exception if are both None."""
+        if dn == None and self.base == None:
+            raise LDAPError, ({'desc':'base not set'},)
+        elif dn == None: dn = self.base
+        else: self.base = dn
+        return dn
+        
+    def set_search_base(self, dn=None, search='cn=*'):
+        """Search the given dn and add found root nodes to the tree."""
+        dn = self.__check_base(dn)
+        nn = self.__connection.search_s(dn, search, ldap.SCOPE_SUBTREE)
+        self.freeze() ; self.clear()
+        self.roots = []
+        for n in nn:
+            node = self.insert_node(None, None, ['dn', n.dn, ''], 0,
+                                    None, None, None, None, FALSE, FALSE)
+            self.__fill(node, n)
+            n.__gtk_expanded = 0
+            self.node_set_row_data(node, n)
+            self.roots.append(n)
+        self.thaw()
+    
+    def set_browse_base(self, dn=None):
+        """Browse the given dn and add the only root node to the tree."""
+        dn = self.__check_base(dn)
+        self.freeze() ; self.clear()
+        base = self.__connection.root_s(dn)
+        basen = self.insert_node(None, None, ['dn', dn, ''], 0,
+                               None, None, None, None, FALSE, FALSE)
+        self.__fill(basen, base)
+        base.__gtk_expanded = 0
+        self.node_set_row_data(basen, base)
+        self.roots = [base]
+        self.thaw()
+
+    def node_get_ldap_data(self, node):
+        """Get node type and value (can be LDAP dn or plain attribute.)
+
+           Return tuple formed of node type and value (note that when the
+           node is of type dn, the fully qualified value is returned, not
+           the reduced one.)
+        """
+        entry = self.node_get_row_data(node)
+        k = self.node_get_text(node, 0)
+        if k == 'dn': return ('dn', entry.dn)
+        else: return (k, self.node_get_text(node, 2))
+
+    def node_set_ldap_data(self, node, value):
+        """Set LDAP data. Multiple attributes are treated correctly.
+
+           If the node is a non-dn one, simply set the new data and colour
+           the node in the `changed' color. If the node is a dn one, returns
+           an error and leave it as it is.
+        """
+        entry = self.node_get_row_data(node)
+        k = self.node_get_text(node, 0)
+        old = self.node_get_text(node, 2)
+        if value == old: return 0
+        elif k == 'dn': return -1
+        else:
+            entry.replace(k, old, value)
+            self.node_set_text(node, 2, value)
+            self.node_set_foreground(node, self.color_data_modified)
+            return 1
+
+    def node_add_ldap_data(self, node, key, value):
+        """Set LDAP by creating a new node.
+
+           Node should be a dn one. A new sub-node is added and its attribute
+           name and value are set respectively to key and value.
+        """
+        entry = self.node_get_row_data(node)
+        if self.node_get_text(node, 0) != 'dn':
+            raise LDAPError, ({'desc':'not a dn node'},)
+        n = self.insert_node(node, None, [key, key, value], 0,
+                             None, None, None, None, FALSE, TRUE)
+        self.node_set_foreground(n, self.color_data)
+        self.node_set_row_data(n, entry)       
+        entry.set(key, value)
+
+
+
+
+
+
+
+
+
+
+