Visitar URL original
Allow for Modlist with ordered attribute values · tacerus/python-ldap@f7977ce · GitHub
Skip to content

Commit f7977ce

Browse files
tacerusspaceone
andcommitted
Allow for Modlist with ordered attribute values
In some LDAP implementations attribute ordering is relevant. Allow for modifications which consider the order of attributes by implementing an optional "strict_order_attr_types" parameter which takes a list of attributes to exempt from the transformation to a set. Signed-off-by: Georg Pfuetzenreuter <georg.pfuetzenreuter@suse.com> Co-authored-by: Florian Best <ldap@freeiam.org>
1 parent 643163a commit f7977ce

3 files changed

Lines changed: 113 additions & 11 deletions

File tree

‎Doc/reference/ldap-modlist.rst‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ The :mod:`ldap.modlist` module defines the following functions:
2020
in the result at all.
2121

2222

23-
.. function:: modifyModlist( old_entry, new_entry [, ignore_attr_types=[] [, ignore_oldexistent=0 [, case_ignore_attr_types=None]]]) -> list
23+
.. function:: modifyModlist( old_entry, new_entry [, ignore_attr_types=[] [, ignore_oldexistent=0 [, case_ignore_attr_types=None, strict_order_attr_types=None]]]) -> list
2424

2525
This function builds a list suitable for passing it directly as argument
2626
*modlist* to method :py:meth:`ldap.ldapobject.LDAPObject.modify` or
@@ -49,6 +49,11 @@ The :mod:`ldap.modlist` module defines the following functions:
4949
situations where a LDAP server normalizes values and one wants to avoid
5050
unnecessary changes (e.g. case of attribute type names in DNs).
5151

52+
*strict_order_attr_types* is a list of attribute type names for which
53+
value ordering should be considered - differences in the order of values
54+
without a difference in the value contents will be treated as a change
55+
for these attribute types instead of being ignored.
56+
5257
.. note::
5358
Replacing attribute values is always done with a
5459
:py:const:`ldap.MOD_DELETE`/:py:const:`ldap.MOD_ADD` pair instead of

‎Lib/ldap/modlist.py‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def modifyModlist(
4040
ignore_attr_types: list[str] | None = None,
4141
ignore_oldexistent: int = 0,
4242
case_ignore_attr_types: list[str] | None = None,
43+
strict_order_attr_types: list[str] | None = None,
4344
) -> LDAPModifyModList:
4445
"""
4546
Build differential modify list for calling LDAPObject.modify()/modify_s()
@@ -59,9 +60,13 @@ def modifyModlist(
5960
case_ignore_attr_types
6061
List of attribute type names for which comparison will be made
6162
case-insensitive
63+
strict_order_attr_types
64+
List of attribute type names for which comparison will be made
65+
with strict ordering (by default, value ordering changes will be ignored)
6266
"""
6367
ignore_attr_types_set = {v.lower() for v in ignore_attr_types or []}
6468
case_ignore_attr_types_set = {v.lower() for v in case_ignore_attr_types or []}
69+
strict_order_attr_types_set = {v.lower() for v in strict_order_attr_types or []}
6570
modlist: list[LDAPModListModifyEntry] = []
6671
attrtype_lower_map = {}
6772
for a in old_entry:
@@ -85,16 +90,19 @@ def modifyModlist(
8590
elif old_value and new_value:
8691
# Replace existing attribute
8792
replace_attr_value = len(old_value) != len(new_value)
93+
8894
if not replace_attr_value:
89-
if attrtype_lower in case_ignore_attr_types_set:
90-
old_value_set = {v.lower() for v in old_value}
91-
new_value_set = {v.lower() for v in new_value}
92-
else:
93-
old_value_set = set(old_value)
94-
new_value_set = set(new_value)
95-
replace_attr_value = new_value_set != old_value_set
95+
normalize = bytes.lower if attrtype_lower in case_ignore_attr_types_set else lambda v: v
96+
collection = list if attrtype_lower in strict_order_attr_types_set else set
97+
replace_attr_value = collection(map(normalize, old_value)) != collection(map(normalize, new_value))
98+
9699
if replace_attr_value:
97-
modlist.extend(((ldap.MOD_DELETE, attrtype, None), (ldap.MOD_ADD, attrtype, new_value)))
100+
modlist.extend(
101+
(
102+
(ldap.MOD_DELETE, attrtype, None),
103+
(ldap.MOD_ADD, attrtype, new_value),
104+
)
105+
)
98106
elif old_value and not new_value:
99107
# Completely delete an existing attribute
100108
modlist.append((ldap.MOD_DELETE, attrtype, None))

‎Tests/t_ldap_modlist.py‎

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def test_addModlist(self):
6464
'mail': [b'michael@stroeder.com'],
6565
},
6666
[],
67+
[],
6768
[
6869
(ldap.MOD_DELETE, 'objectClass', None),
6970
(ldap.MOD_ADD, 'objectClass', [b'person', b'inetOrgPerson']),
@@ -74,6 +75,80 @@ def test_addModlist(self):
7475
(ldap.MOD_ADD, 'enum', [b'a', b'b', b'd']),
7576
],
7677
),
78+
(
79+
{
80+
'objectClass': [b'person', b'pilotPerson'],
81+
'cn': [b'Max Mustermann'],
82+
'sn': [b'Mustermann'],
83+
'mail': [b'max@example.com'],
84+
},
85+
{
86+
'objectClass': [b'person', b'pilotPerson'],
87+
'cn': [b'Max Mustermann'],
88+
'sn': [b'Mustermann'],
89+
'mail': [b'max@example.com'],
90+
},
91+
[],
92+
['mail'], # strict_order_attr_types
93+
[],
94+
),
95+
(
96+
{
97+
'objectClass': [b'person', b'pilotPerson'],
98+
'cn': [b'Max Mustermann'],
99+
'sn': [b'Mustermann'],
100+
'mail': [b'max@example.com'],
101+
},
102+
{
103+
'objectClass': [b'person', b'pilotPerson'],
104+
'cn': [b'Max Mustermann'],
105+
'sn': [b'Mustermann'],
106+
'mail': [b'max@example.com', b'max@example.net'],
107+
},
108+
[],
109+
['mail'], # strict_order_attr_types
110+
[
111+
(ldap.MOD_DELETE, 'mail', None),
112+
(ldap.MOD_ADD, 'mail', [b'max@example.com', b'max@example.net']),
113+
],
114+
),
115+
(
116+
{
117+
'objectClass': [b'person', b'pilotPerson'],
118+
'cn': [b'Max Mustermann'],
119+
'sn': [b'Mustermann'],
120+
'mail': [b'max@example.com', b'max@example.net'],
121+
},
122+
{
123+
'objectClass': [b'person', b'pilotPerson'],
124+
'cn': [b'Max Mustermann'],
125+
'sn': [b'Mustermann'],
126+
'mail': [b'max@example.net', b'max@example.com'],
127+
},
128+
[],
129+
['mail'], # strict_order_attr_types
130+
[
131+
(ldap.MOD_DELETE, 'mail', None),
132+
(ldap.MOD_ADD, 'mail', [b'max@example.net', b'max@example.com']),
133+
],
134+
),
135+
(
136+
{
137+
'objectClass': [b'person', b'pilotPerson'],
138+
'cn': [b'Max Mustermann'],
139+
'sn': [b'Mustermann'],
140+
'mail': [b'max@example.com', b'max@example.net'],
141+
},
142+
{
143+
'objectClass': [b'person', b'pilotPerson'],
144+
'cn': [b'Max Mustermann'],
145+
'sn': [b'Mustermann'],
146+
'mail': [b'max@example.net', b'max@example.com'],
147+
},
148+
[],
149+
[], # strict_order_attr_types
150+
[],
151+
),
77152
(
78153
{
79154
'c': [b'DE'],
@@ -82,6 +157,7 @@ def test_addModlist(self):
82157
'c': [b'FR'],
83158
},
84159
[],
160+
[],
85161
[
86162
(ldap.MOD_DELETE, 'c', None),
87163
(ldap.MOD_ADD, 'c', [b'FR']),
@@ -102,6 +178,7 @@ def test_addModlist(self):
102178
'sn': [None],
103179
},
104180
[],
181+
[],
105182
[
106183
(ldap.MOD_DELETE, 'c', None),
107184
(ldap.MOD_DELETE, 'objectClass', None),
@@ -122,6 +199,7 @@ def test_addModlist(self):
122199
'enum': [b'a', b'b', b'c'],
123200
},
124201
['objectClass'],
202+
[],
125203
[
126204
(ldap.MOD_DELETE, 'sn', None),
127205
(ldap.MOD_DELETE, 'enum', None),
@@ -131,9 +209,20 @@ def test_addModlist(self):
131209
]
132210

133211
def test_modifyModlist(self):
134-
for old_entry, new_entry, case_ignore_attr_types, test_modlist in self.modifyModlist_tests:
212+
for (
213+
old_entry,
214+
new_entry,
215+
case_ignore_attr_types,
216+
strict_order_attr_types,
217+
test_modlist,
218+
) in self.modifyModlist_tests:
135219
test_modlist.sort()
136-
result_modlist = modifyModlist(old_entry, new_entry, case_ignore_attr_types=case_ignore_attr_types)
220+
result_modlist = modifyModlist(
221+
old_entry,
222+
new_entry,
223+
case_ignore_attr_types=case_ignore_attr_types,
224+
strict_order_attr_types=strict_order_attr_types,
225+
)
137226
result_modlist.sort()
138227

139228
self.assertEqual(

0 commit comments

Comments
 (0)