diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py new file mode 100644 index 0000000..b0fe364 --- /dev/null +++ b/Lib/ldap/modlist.py @@ -0,0 +1,126 @@ +""" +ldap.modlist - create add/modify modlist's + +See http://www.python-ldap.org/ for details. + +$Id: modlist.py,v 1.18 2011/06/06 13:07:38 stroeder Exp $ + +Python compability note: +This module is known to work with Python 2.0+ but should work +with Python 1.5.2 as well. +""" + +from ldap import __version__ + +import string,ldap,ldap.cidict + + +def list_dict(l,case_insensitive=0): + """ + return a dictionary with all items of l being the keys of the dictionary + + If argument case_insensitive is non-zero ldap.cidict.cidict will be + used for case-insensitive string keys + """ + if case_insensitive: + d = ldap.cidict.cidict() + else: + d = {} + for i in l: + d[i]=None + return d + + +def addModlist(entry,ignore_attr_types=None): + """Build modify list for call of method LDAPObject.add()""" + ignore_attr_types = list_dict(map(string.lower,(ignore_attr_types or []))) + modlist = [] + for attrtype in entry.keys(): + if ignore_attr_types.has_key(string.lower(attrtype)): + # This attribute type is ignored + continue + # Eliminate empty attr value strings in list + attrvaluelist = filter(lambda x:x!=None,entry[attrtype]) + if attrvaluelist: + modlist.append((attrtype,entry[attrtype])) + return modlist # addModlist() + + +def modifyModlist( + old_entry,new_entry,ignore_attr_types=None,ignore_oldexistent=0,case_ignore_attr_types=None +): + """ + Build differential modify list for calling LDAPObject.modify()/modify_s() + + old_entry + Dictionary holding the old entry + new_entry + Dictionary holding what the new entry should be + ignore_attr_types + List of attribute type names to be ignored completely + ignore_oldexistent + If non-zero attribute type names which are in old_entry + but are not found in new_entry at all are not deleted. + This is handy for situations where your application + sets attribute value to '' for deleting an attribute. + In most cases leave zero. + case_ignore_attr_types + List of attribute type names for which comparison will be made + case-insensitive + """ + ignore_attr_types = list_dict(map(string.lower,(ignore_attr_types or []))) + case_ignore_attr_types = list_dict(map(string.lower,(case_ignore_attr_types or []))) + modlist = [] + attrtype_lower_map = {} + for a in old_entry.keys(): + attrtype_lower_map[string.lower(a)]=a + for attrtype in new_entry.keys(): + attrtype_lower = string.lower(attrtype) + if ignore_attr_types.has_key(attrtype_lower): + # This attribute type is ignored + continue + # Filter away null-strings + new_value = filter(lambda x:x!=None,new_entry[attrtype]) + if attrtype_lower_map.has_key(attrtype_lower): + old_value = old_entry.get(attrtype_lower_map[attrtype_lower],[]) + old_value = filter(lambda x:x!=None,old_value) + del attrtype_lower_map[attrtype_lower] + else: + old_value = [] + if not old_value and new_value: + # Add a new attribute to entry + modlist.append((ldap.MOD_ADD,attrtype,new_value)) + elif old_value and new_value: + # Replace existing attribute + replace_attr_value = len(old_value)!=len(new_value) + if not replace_attr_value: + case_insensitive = case_ignore_attr_types.has_key(attrtype_lower) + old_value_dict=list_dict(old_value,case_insensitive) + new_value_dict=list_dict(new_value,case_insensitive) + delete_values = [] + for v in old_value: + if not new_value_dict.has_key(v): + replace_attr_value = 1 + break + add_values = [] + if not replace_attr_value: + for v in new_value: + if not old_value_dict.has_key(v): + replace_attr_value = 1 + break + if replace_attr_value: + modlist.append((ldap.MOD_DELETE,attrtype,None)) + modlist.append((ldap.MOD_ADD,attrtype,new_value)) + elif old_value and not new_value: + # Completely delete an existing attribute + modlist.append((ldap.MOD_DELETE,attrtype,None)) + if not ignore_oldexistent: + # Remove all attributes of old_entry which are not present + # in new_entry at all + for a in attrtype_lower_map.keys(): + if ignore_attr_types.has_key(a): + # This attribute type is ignored + continue + attrtype = attrtype_lower_map[a] + modlist.append((ldap.MOD_DELETE,attrtype,None)) + return modlist # modifyModlist() diff --git a/Tests/Lib/ldap/test_modlist.py b/Tests/Lib/ldap/test_modlist.py new file mode 100644 index 0000000..fef0f65 --- /dev/null +++ b/Tests/Lib/ldap/test_modlist.py @@ -0,0 +1,137 @@ +""" +Tests for module ldap.modlist +""" + +import ldap + +from ldap.modlist import addModlist,modifyModlist + +print '\nTesting function addModlist():' +addModlist_tests = [ + ( + { + 'objectClass':['person','pilotPerson'], + 'cn':['Michael Str\303\266der','Michael Stroeder'], + 'sn':['Str\303\266der'], + 'dummy1':[], + 'dummy2':['2'], + 'dummy3':[''], + }, + [ + ('objectClass',['person','pilotPerson']), + ('cn',['Michael Str\303\266der','Michael Stroeder']), + ('sn',['Str\303\266der']), + ('dummy2',['2']), + ('dummy3',['']), + ] + ), +] +for entry,test_modlist in addModlist_tests: + test_modlist.sort() + result_modlist = addModlist(entry) + result_modlist.sort() + if test_modlist!=result_modlist: + print 'addModlist(%s) returns\n%s\ninstead of\n%s.' % ( + repr(entry),repr(result_modlist),repr(test_modlist) + ) + +print '\nTesting function modifyModlist():' +modifyModlist_tests = [ + + ( + { + 'objectClass':['person','pilotPerson'], + 'cn':['Michael Str\303\266der','Michael Stroeder'], + 'sn':['Str\303\266der'], + 'enum':['a','b','c'], + 'c':['DE'], + }, + { + 'objectClass':['person','inetOrgPerson'], + 'cn':['Michael Str\303\266der','Michael Stroeder'], + 'sn':[], + 'enum':['a','b','d'], + 'mail':['michael@stroeder.com'], + }, + [], + [ + (ldap.MOD_DELETE,'objectClass',None), + (ldap.MOD_ADD,'objectClass',['person','inetOrgPerson']), + (ldap.MOD_DELETE,'c',None), + (ldap.MOD_DELETE,'sn',None), + (ldap.MOD_ADD,'mail',['michael@stroeder.com']), + (ldap.MOD_DELETE,'enum',None), + (ldap.MOD_ADD,'enum',['a','b','d']), + ] + ), + + ( + { + 'c':['DE'], + }, + { + 'c':['FR'], + }, + [], + [ + (ldap.MOD_DELETE,'c',None), + (ldap.MOD_ADD,'c',['FR']), + ] + ), + + # Now a weird test-case for catching all possibilities + # of removing an attribute with MOD_DELETE,attr_type,None + ( + { + 'objectClass':['person'], + 'cn':[None], + 'sn':[''], + 'c':['DE'], + }, + { + 'objectClass':[], + 'cn':[], + 'sn':[None], + }, + [], + [ + (ldap.MOD_DELETE,'c',None), + (ldap.MOD_DELETE,'objectClass',None), + (ldap.MOD_DELETE,'sn',None), + ] + ), + + ( + { + 'objectClass':['person'], + 'cn':['Michael Str\303\266der','Michael Stroeder'], + 'sn':['Str\303\266der'], + 'enum':['a','b','C'], + }, + { + 'objectClass':['Person'], + 'cn':['Michael Str\303\266der','Michael Stroeder'], + 'sn':[], + 'enum':['a','b','c'], + }, + ['objectClass'], + [ + (ldap.MOD_DELETE,'sn',None), + (ldap.MOD_DELETE,'enum',None), + (ldap.MOD_ADD,'enum',['a','b','c']), + ] + ), + +] +for old_entry,new_entry,case_ignore_attr_types,test_modlist in modifyModlist_tests: + test_modlist.sort() + result_modlist = modifyModlist(old_entry,new_entry,case_ignore_attr_types=case_ignore_attr_types) + result_modlist.sort() + + if test_modlist!=result_modlist: + print 'modifyModlist(%s,%s) returns\n%s\ninstead of\n%s.' % ( + repr(old_entry), + repr(new_entry), + repr(result_modlist), + repr(test_modlist) + )