Fix: Auto-sync displayName with cn in LdapUser.save()

- Added save() override in LdapUser to auto-update displayName from cn
- Fixed user_service to set ldap_user.cn BEFORE save() (not after)
- This ensures displayName is synced when givenName/sn change
- Previously displayName wasn't updated because cn was set after save()
- Now: set cn → save() → displayName auto-synced → modify_dn()
This commit is contained in:
lizerk 2026-03-20 01:09:24 +03:00
parent 649bd62a2b
commit 7b7e071df1
2 changed files with 34 additions and 9 deletions

View File

@ -264,6 +264,21 @@ 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

@ -753,7 +753,7 @@ class UserService(BaseService):
setattr(ldap_user, orm_field, val if val else ".")
orm_dirty = True
# Вычисляем новый cn если изменились имя/фамилия (НЕ устанавливаем в объект!)
# Вычисляем новый cn если изменились имя/фамилия
new_cn = None
if "first_name" in model_changes or "last_name" in model_changes:
# Формируем новый cn из имени и фамилии
@ -766,7 +766,13 @@ class UserService(BaseService):
if sn_str and sn_str != ".":
parts.append(sn_str)
new_cn = " ".join(parts) if parts else ldap_user.sam_account_name
new_cn = " ".join(parts) if parts else get_ldap_str(ldap_user.sam_account_name)
# Устанавливаем новый cn в объект ДО save()
# чтобы LdapUser.save() автоматически обновил displayName
if new_cn != old_cn:
ldap_user.cn = new_cn
orm_dirty = True
# 5) Аватар
avatar_saved = False
@ -778,7 +784,7 @@ class UserService(BaseService):
avatar_saved = True
if orm_dirty:
# save() автоматически синхронизирует displayName (см. LdapUser.save())
# save() автоматически синхронизирует displayName с cn (см. LdapUser.save())
ldap_user.save()
# Переименование DN если cn изменился
@ -789,16 +795,20 @@ class UserService(BaseService):
container_dn = parts[1]
new_rdn = f"CN={esc_rdn(new_cn)}" # новый RDN с экранированием спецсимволов
# modify_dn для переименования (остаёмся в том же контейнере)
# modify_dn для переименования DN (атрибуты уже обновлены через save)
ok = conn.modify_dn(dn, new_rdn)
if ok:
dn = f"{new_rdn},{container_dn}"
# Обновляем cn в объекте ПОСЛЕ успешного переименования
ldap_user.cn = new_cn
# Обновляем _saved_dn для корректности последующих операций
ldap_user._saved_dn = dn
ldap_user.dn = dn
else:
# Если переименование не удалось, логируем но продолжаем
# (атрибуты уже обновлены, только DN остался старым)
pass
# Если переименование не удалось, откатываем cn к старому значению
# (атрибуты уже обновлены с новым cn, но DN остался старым - несоответствие!)
logger.warning(
f"Failed to rename DN from {dn} to {new_rdn},{container_dn}. "
f"User attributes updated but DN unchanged."
)
# 6) Группы (ORM)
if group_cns is not None: