diff --git a/Doc/reference/ldap-modlist.rst b/Doc/reference/ldap-modlist.rst index 64bf5787..884bd497 100644 --- a/Doc/reference/ldap-modlist.rst +++ b/Doc/reference/ldap-modlist.rst @@ -20,7 +20,7 @@ The :mod:`ldap.modlist` module defines the following functions: in the result at all. -.. function:: modifyModlist( old_entry, new_entry [, ignore_attr_types=[] [, ignore_oldexistent=0 [, case_ignore_attr_types=None]]]) -> list +.. function:: modifyModlist( old_entry, new_entry [, ignore_attr_types=[] [, ignore_oldexistent=0 [, case_ignore_attr_types=None, strict_order_attr_types=None]]]) -> list This function builds a list suitable for passing it directly as argument *modlist* to method :py:meth:`ldap.ldapobject.LDAPObject.modify` or @@ -49,6 +49,11 @@ The :mod:`ldap.modlist` module defines the following functions: situations where a LDAP server normalizes values and one wants to avoid unnecessary changes (e.g. case of attribute type names in DNs). + *strict_order_attr_types* is a list of attribute type names for which + value ordering should be considered - differences in the order of values + without a difference in the value contents will be treated as a change + for these attribute types instead of being ignored. + .. note:: Replacing attribute values is always done with a :py:const:`ldap.MOD_DELETE`/:py:const:`ldap.MOD_ADD` pair instead of diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index bf4e4819..c8ccbce4 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -25,7 +25,7 @@ def addModlist(entry,ignore_attr_types=None): def modifyModlist( - old_entry,new_entry,ignore_attr_types=None,ignore_oldexistent=0,case_ignore_attr_types=None + old_entry,new_entry,ignore_attr_types=None,ignore_oldexistent=0,case_ignore_attr_types=None, strict_order_attr_types=None, ): """ Build differential modify list for calling LDAPObject.modify()/modify_s() @@ -45,9 +45,13 @@ def modifyModlist( case_ignore_attr_types List of attribute type names for which comparison will be made case-insensitive + strict_order_attr_types + List of attribute type names for which comparison will be made + with strict ordering (by default, value ordering changes will be ignored) """ ignore_attr_types = {v.lower() for v in ignore_attr_types or []} case_ignore_attr_types = {v.lower() for v in case_ignore_attr_types or []} + strict_order_attr_types = {v.lower() for v in strict_order_attr_types or []} modlist = [] attrtype_lower_map = {} for a in old_entry: @@ -73,11 +77,19 @@ def modifyModlist( replace_attr_value = len(old_value)!=len(new_value) if not replace_attr_value: if attrtype_lower in case_ignore_attr_types: - old_value_set = {v.lower() for v in old_value} - new_value_set = {v.lower() for v in new_value} + if attrtype_lower in strict_order_attr_types: + old_value_set = [v.lower() for v in old_value] + new_value_set = [v.lower() for v in new_value] + else: + old_value_set = {v.lower() for v in old_value} + new_value_set = {v.lower() for v in new_value} else: - old_value_set = set(old_value) - new_value_set = set(new_value) + if attrtype_lower in strict_order_attr_types: + old_value_set = [old_value] + new_value_set = [new_value] + else: + old_value_set = set(old_value) + new_value_set = set(new_value) replace_attr_value = new_value_set != old_value_set if replace_attr_value: modlist.append((ldap.MOD_DELETE,attrtype,None)) diff --git a/Tests/t_ldap_modlist.py b/Tests/t_ldap_modlist.py index 62596247..c17aaaef 100644 --- a/Tests/t_ldap_modlist.py +++ b/Tests/t_ldap_modlist.py @@ -65,6 +65,7 @@ def test_addModlist(self): 'mail':[b'michael@stroeder.com'], }, [], + [], [ (ldap.MOD_DELETE,'objectClass',None), (ldap.MOD_ADD,'objectClass',[b'person',b'inetOrgPerson']), @@ -76,6 +77,84 @@ def test_addModlist(self): ] ), + ( + { + 'objectClass':[b'person',b'pilotPerson'], + 'cn':[b'Max Mustermann'], + 'sn':[b'Mustermann'], + 'mail':[b'max@example.com'], + }, + { + 'objectClass':[b'person',b'pilotPerson'], + 'cn':[b'Max Mustermann'], + 'sn':[b'Mustermann'], + 'mail':[b'max@example.com'], + }, + [], + ['mail'], # strict_order_attr_types + [] + ), + + ( + { + 'objectClass':[b'person',b'pilotPerson'], + 'cn':[b'Max Mustermann'], + 'sn':[b'Mustermann'], + 'mail':[b'max@example.com'], + }, + { + 'objectClass':[b'person',b'pilotPerson'], + 'cn':[b'Max Mustermann'], + 'sn':[b'Mustermann'], + 'mail':[b'max@example.com', b'max@example.net'], + }, + [], + ['mail'], # strict_order_attr_types + [ + (ldap.MOD_DELETE,'mail',None), + (ldap.MOD_ADD,'mail',[b'max@example.com', b'max@example.net']), + ] + ), + + ( + { + 'objectClass':[b'person',b'pilotPerson'], + 'cn':[b'Max Mustermann'], + 'sn':[b'Mustermann'], + 'mail':[b'max@example.com', b'max@example.net'], + }, + { + 'objectClass':[b'person',b'pilotPerson'], + 'cn':[b'Max Mustermann'], + 'sn':[b'Mustermann'], + 'mail':[b'max@example.net', b'max@example.com'], + }, + [], + ['mail'], # strict_order_attr_types + [ + (ldap.MOD_DELETE,'mail',None), + (ldap.MOD_ADD,'mail',[b'max@example.net', b'max@example.com']), + ] + ), + + ( + { + 'objectClass':[b'person',b'pilotPerson'], + 'cn':[b'Max Mustermann'], + 'sn':[b'Mustermann'], + 'mail':[b'max@example.com', b'max@example.net'], + }, + { + 'objectClass':[b'person',b'pilotPerson'], + 'cn':[b'Max Mustermann'], + 'sn':[b'Mustermann'], + 'mail':[b'max@example.net', b'max@example.com'], + }, + [], + [], # strict_order_attr_types + [] + ), + ( { 'c':[b'DE'], @@ -84,6 +163,7 @@ def test_addModlist(self): 'c':[b'FR'], }, [], + [], [ (ldap.MOD_DELETE,'c',None), (ldap.MOD_ADD,'c',[b'FR']), @@ -105,6 +185,7 @@ def test_addModlist(self): 'sn':[None], }, [], + [], [ (ldap.MOD_DELETE,'c',None), (ldap.MOD_DELETE,'objectClass',None), @@ -126,6 +207,7 @@ def test_addModlist(self): 'enum':[b'a',b'b',b'c'], }, ['objectClass'], + [], [ (ldap.MOD_DELETE,'sn',None), (ldap.MOD_DELETE,'enum',None), @@ -136,11 +218,13 @@ def test_addModlist(self): ] def test_modifyModlist(self): - for old_entry, new_entry, case_ignore_attr_types, test_modlist in self.modifyModlist_tests: + for old_entry, new_entry, case_ignore_attr_types, strict_order_attr_types, test_modlist in self.modifyModlist_tests: test_modlist.sort() result_modlist = modifyModlist( old_entry, new_entry, - case_ignore_attr_types=case_ignore_attr_types) + case_ignore_attr_types=case_ignore_attr_types, + strict_order_attr_types=strict_order_attr_types, + ) result_modlist.sort() self.assertEqual(