Fix: Don't modify CN via save(), use modify_dn only

AD forbids modifying RDN (CN) through modify_s - must use rename.
- Removed LdapUser.save() override that synced cn→displayName
  (this caused cn to be in modlist → NOT_ALLOWED_ON_RDN error)
- user_service now sets displayName explicitly instead of cn
- CN is only changed via conn.modify_dn() after save()
- Flow: set displayName + other attrs → save() → modify_dn(cn)
This commit is contained in:
lizerk 2026-03-20 01:11:44 +03:00
parent 7b7e071df1
commit afd66662ce
2 changed files with 5 additions and 20 deletions

View File

@ -264,21 +264,6 @@ class LdapUser(LdapSyncStateMixin, ModifyDnMixin, LdapModel):
class Meta:
managed = False # Django не управляет схемой LDAP
def save(self, *args, **kwargs):
"""Переопределяем save для автосинхронизации displayName с cn.
Active Directory обычно синхронизирует displayName с cn,
но при обновлении атрибутов через ldapdb это нужно делать вручную.
"""
# Синхронизируем displayName с cn если cn установлен
if self.cn and not self.display_name:
self.display_name = self.cn
elif self.cn and self.display_name != self.cn:
# Если cn изменился, обновляем displayName
self.display_name = self.cn
return super().save(*args, **kwargs)
def __str__(self):
return f"{self.display_name} ({self.sam_account_name})"

View File

@ -768,10 +768,11 @@ class UserService(BaseService):
new_cn = " ".join(parts) if parts else get_ldap_str(ldap_user.sam_account_name)
# Устанавливаем новый cn в объект ДО save()
# чтобы LdapUser.save() автоматически обновил displayName
# НЕ ставим cn до save() — AD запрещает modify CN через modify_s,
# только через rename (modify_dn).
# Вместо этого ставим displayName вручную.
if new_cn != old_cn:
ldap_user.cn = new_cn
ldap_user.display_name = new_cn
orm_dirty = True
# 5) Аватар
@ -784,10 +785,9 @@ class UserService(BaseService):
avatar_saved = True
if orm_dirty:
# save() автоматически синхронизирует displayName с cn (см. LdapUser.save())
ldap_user.save()
# Переименование DN если cn изменился
# Переименование DN если cn изменился (через modify_dn, не modify_s)
if new_cn and new_cn != old_cn:
# Извлекаем текущий контейнер (всё после первой запятой)
parts = dn.split(",", 1)