From afd66662ce45b8efcf9cd276966a0024d2829a5a Mon Sep 17 00:00:00 2001 From: lizerk Date: Fri, 20 Mar 2026 01:11:44 +0300 Subject: [PATCH] Fix: Don't modify CN via save(), use modify_dn only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- backend/employees/ldap/orm_models.py | 15 --------------- backend/employees/ldap/services/user_service.py | 10 +++++----- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/backend/employees/ldap/orm_models.py b/backend/employees/ldap/orm_models.py index dee858c2..52b9faa6 100644 --- a/backend/employees/ldap/orm_models.py +++ b/backend/employees/ldap/orm_models.py @@ -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})" diff --git a/backend/employees/ldap/services/user_service.py b/backend/employees/ldap/services/user_service.py index 8a2cee0f..f3e49437 100644 --- a/backend/employees/ldap/services/user_service.py +++ b/backend/employees/ldap/services/user_service.py @@ -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)