diff --git a/backend/employees/ldap/orm_models.py b/backend/employees/ldap/orm_models.py index 52b9faa6..dee858c2 100644 --- a/backend/employees/ldap/orm_models.py +++ b/backend/employees/ldap/orm_models.py @@ -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})" diff --git a/backend/employees/ldap/services/user_service.py b/backend/employees/ldap/services/user_service.py index 92d9c469..8a2cee0f 100644 --- a/backend/employees/ldap/services/user_service.py +++ b/backend/employees/ldap/services/user_service.py @@ -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: