Hello.
Yesterday we found a bug in s4-connector when synchronizing AD > UCS. The error is related to incorrect determination of the need to update the uniqueMember group attribute. In the log /var/log/univention/connector-s4.log
there are Tracebacks of the form:
12.11.2023 00:00:07.280 LDAP (PROCESS): Building internal group membership cache
12.11.2023 00:00:07.315 LDAP (PROCESS): Internal group membership cache was created
12.11.2023 00:00:07.859 LDAP (PROCESS): sync AD > UCS: Resync rejected dn: 'CN=test,OU=ТПшки,OU=<masked>,OU=Users,DC=<masked>'
12.11.2023 00:00:07.872 LDAP (PROCESS): sync AD > UCS: [ user] [ modify] 'uid=test,ou=тпшки,ou=<masked>,ou=users,dc=<masked>'
12.11.2023 00:00:07.944 LDAP (ERROR ): failed in post_con_modify_functions
12.11.2023 00:00:07.948 LDAP (ERROR ): Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/univention/s4connector/__init__.py", line 1503, in sync_to_ucs
post_ucs_modify_function(self, property_type, object)
File "/usr/lib/python3/dist-packages/univention/s4connector/s4/__init__.py", line 86, in object_memberships_sync_to_ucs
return connector.object_memberships_sync_to_ucs(key, object)
File "/usr/lib/python3/dist-packages/univention/s4connector/s4/__init__.py", line 1402, in object_memberships_sync_to_ucs
self.one_group_member_sync_to_ucs(ucs_group_object, object)
File "/usr/lib/python3/dist-packages/univention/s4connector/s4/__init__.py", line 1435, in one_group_member_sync_to_ucs
self.lo.lo.modify_s(ucs_group_object['dn'], ml)
File "/usr/lib/python3/dist-packages/univention/uldap.py", line 212, in _decorated
return func(self, *args, **kwargs)
File "/usr/lib/python3/dist-packages/univention/uldap.py", line 801, in modify_s
self.lo.modify_ext_s(dn, ml)
File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 1253, in modify_ext_s
return self._apply_method_s(SimpleLDAPObject.modify_ext_s,*args,**kwargs)
File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 1197, in _apply_method_s
return func(self,*args,**kwargs)
File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 602, in modify_ext_s
resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 749, in result3
resp_ctrl_classes=resp_ctrl_classes
File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 756, in result4
ldap_result = self._ldap_call(self._l.result4,msgid,all,timeout,add_ctrls,add_intermediates,add_extop)
File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 329, in _ldap_call
reraise(exc_type, exc_value, exc_traceback)
File "/usr/lib/python3/dist-packages/ldap/compat.py", line 44, in reraise
raise exc_value
File "/usr/lib/python3/dist-packages/ldap/ldapobject.py", line 313, in _ldap_call
result = func(*args,**kwargs)
ldap.TYPE_OR_VALUE_EXISTS: {'desc': 'Type or value exists', 'info': 'modify/add: uniqueMember: value #0 already exists'}
(We use Cyrillic characters in ou names )
The error is related to passing incorrect arguments to the _compare_lowercase
function in the /usr/lib/python3/dist-packages/univention/s4connector/s4/__init__.py
module (line 1416)
An incorrect function call occurs on line 1425:
self.__compare_lowercase(object['dn'].encode('UTF-8'), ucs_group_object['attributes'].get('uniqueMember', [])):
ucs_group_object['attributes'].get('uniqueMember', [])
contains a string unprocessed by the lower()
method but encoded with encode('UTF-8')
, while the string object['dn']
is processed by the lower()
method.
As a result, __compare_lowercase()
always returns False
at this location, and the LDAP module returns a TYPE_OR_VALUE_EXISTS
exception when trying to add the uniqueMember attribute.
For an example, try to find the difference between the strings:
b'uid=<masked>,ou=\xd0\xbe\xd1\x82\xd0\xb4\xd0\xb5\xd0\xbb \xd1\x81\xd0\xb8\xd1\x81\xd1\x82\xd0\xb5\xd0\xbc\xd0\xbd\xd1\x8b\xd1\x85 \xd0\xb0\xd0\xb4\xd0\xbc\xd0\xb8\xd0\xbd\xd0\xb8\xd1\x81\xd1\x82\xd1\x80\xd0\x
b0\xd1\x82\xd0\xbe\xd1\x80\xd0\xbe\xd0\xb2,ou=<masked>,ou=users,dc=<masked>,dc=<masked>,dc=<masked>'
b'uid=<masked>,ou=\xd0\x9e\xd1\x82\xd0\xb4\xd0\xb5\xd0\xbb \xd1\x81\xd0\xb8\xd1\x81\xd1\x82\xd0\xb5\xd0\xbc\xd0\xbd\xd1\x8b\xd1\x85 \xd0\xb0\xd0\xb4\xd0\xbc\xd0\xb8\xd0\xbd\xd0\xb8\xd1\x81\xd1\x82\xd1\x80\xd0\xb0\xd1
\x82\xd0\xbe\xd1\x80\xd0\xbe\xd0\xb2,ou=<masked>,ou=users,dc=<masked>,dc=<masked>,dc=<masked>'
As a quick (and dirty) hotfix, we fixed the __compare_lowercase()
comparison operation as follows:
return any(dn.lower() == d.decode('UTF-8').lower().encode('UTF-8') for d in dn_list)