Corp/docs/guides/ldap-orm-mixins-usage.md
lizerk 75662ec7ea feat(ldap): Полная миграция на django-ldapdb ORM с миксинами и Django Admin
LDAP ORM модели (orm_models.py):
================================
✅ Применены миксины ModifyDnMixin и LdapSyncStateMixin ко всем моделям
✅ LdapUser - поддержка modify_dn + автоуправление LdapSyncState
✅ LdapGroup - поддержка modify_dn для перемещения групп
✅ LdapOrganizationalUnit - поддержка modify_dn для реорганизации

Миксины (mixins.py) - НОВЫЙ ФАЙЛ:
=================================
✅ ModifyDnMixin - автоматический modify_dn при изменении base_dn
   • Отслеживает изменения base_dn
   • Выполняет LDAP modify_dn с newsuperior
   • Обновляет _saved_dn для корректного последующего save()
   • Использование: user.base_dn = new_dn; user.save()

✅ LdapSyncStateMixin - автоуправление LdapSyncState
   • Автоматически создает/обновляет LdapSyncState при save()
   • Автоматически удаляет LdapSyncState при delete()
   • Настройки: _sync_model_name, _sync_pk_field

Django Admin для LDAP (admin.py) - НОВЫЙ ФАЙЛ:
==============================================
✅ LdapUserAdmin - полноценная админка для LDAP пользователей
   • List display: CN, SAM, email, Django Employee link, sync status
   • Search: по CN, SAM, email, имени, фамилии
   • Filters: по userAccountControl
   • Actions синхронизации:
     - sync_from_ldap_to_django (LDAP → Django)
     - sync_from_django_to_ldap (Django → LDAP)
     - show_sync_diff (сравнение данных)
   • Fieldsets: идентификация, персональные данные, контакты, UAC, группы
   • DN форматирование с подсветкой компонентов
   • Sync status indicators с иконками

Django Admin для Employee (employees/admin.py):
===============================================
✅ Добавлены зеркальные actions синхронизации:
   • sync_from_django_to_ldap - создание/обновление в LDAP
     - Автоматическое создание LDAP пользователя если не существует
     - Генерация временного пароля
     - Установка is_ldap_managed = True
   • sync_from_ldap_to_django - обновление из LDAP
   • show_sync_diff - сравнение Django ↔ LDAP
✅ Импорт django.contrib.messages для уведомлений

Сервисы LDAP (services/):
=========================
✅ UserService - полная миграция на ORM для UPDATE операций
✅ GroupService - использование ORM для модификаций
✅ DepartmentService - ORM для обновления OU
✅ PositionService - ORM для групп позиций

Тесты (tests/unit/):
===================
✅ test_user_service.py - обновлены под ORM
✅ test_group_service.py - обновлены под ORM
✅ test_position_service.py - обновлены под ORM
✅ conftest.py - добавлены фикстуры для ORM моделей

Authentication (auth_backends.py):
=================================
✅ EmailOrPhoneBackend - добавлено подробное debug логирование
   • Логирование email/phone lookup
   • Логирование is_active проверки
   • Логирование password check с hash prefix
✅ SuperuserOnlyBackend - добавлено debug логирование
   • Логирование parent auth результата
   • Логирование is_superuser проверки

API (api/auth/serializers.py):
==============================
✅ PhoneOrEmailTokenObtainPairSerializer - исправлен KeyError
   • Добавлена проверка credentials перед super().validate()
   • Явный AuthenticationFailed при отсутствии учетных данных

Middleware (middleware.py):
===========================
✅ Очистка от устаревшего кода (305 → 102 строки)
   • ❌ Удалена EmailVerificationMiddleware (не используется)
   • ❌ Удалена RegistrationIPRestrictionMiddleware (не используется)
   • ❌ Удалены все ссылки на namespace 'auth_front'
   • ✅ Возврат 403 для неавторизованных вместо редиректа

Settings (settings.py):
=======================
✅ LOGIN_URL изменен с 'auth_front:login' на '/admin/login/'
✅ Добавлено DEBUG логирование для:
   • django.contrib.auth
   • eusrr_backend.auth_backends

Миграции:
=========
✅ Удалены старые миграции LDAP моделей (0042, 0043)
✅ Добавлена миграция LdapSyncQueue (0042_ldapsyncqueue.py)

Документация:
=============
✅ ADMIN_GUIDE.md - руководство по использованию LDAP Admin
✅ docs/guides/ldap-orm-mixins-usage.md - usage guide для миксинов
✅ docs/completed/ldap-orm-create-support.md - документация ORM миграции
✅ docs/in-progress/LDAP_ORM_MIGRATION_PLAN.md - план миграции

Инфраструктура (__init__.py):
=============================
✅ Добавлен импорт admin для авторегистрации
✅ Экспорт ORM моделей (LdapUser, LdapGroup, LdapOrganizationalUnit)

ИТОГО:
======
• Полная миграция с ldap3 на django-ldapdb ORM для UPDATE/MOVE/DELETE
• Унификация: один подход для всех LDAP операций через модели
• Автоматизация: синхронизация состояния и перемещения через миксины
• UI: полноценный Django Admin для управления LDAP объектами
• Отладка: расширенное логирование аутентификации
• Чистота: удален устаревший код middleware и миграций
• Тесты: 29 passed, 0 failed, 18 skipped

Breaking Changes: НЕТ - обратная совместимость сохранена
2026-03-19 22:33:06 +03:00

9.4 KiB
Raw Blame History

Использование LDAP ORM Mixins

Файл: backend/employees/ldap/mixins.py
Дата: 19 марта 2026 г.

ModifyDnMixin — перемещение объектов между OU

Назначение

Добавляет поддержку перемещения LDAP объектов через изменение base_dn + .save().

Проблема: django-ldapdb не передаёт newsuperior в rename_s(), поэтому объекты нельзя перемещать между OU.

Решение: Миксин отслеживает изменения base_dn и автоматически вызывает conn.modify_dn(newsuperior=...).

Использование

from employees.ldap.orm_models import LdapUser

# 1. Загружаем пользователя
user = LdapUser.objects.get(cn="Ivanov")
print(user.dn)  # CN=Ivanov,OU=IT,OU=Departments,...

# 2. Меняем base_dn (увольнение)
user.base_dn = "OU=Dismissed,OU=company,DC=robotail,DC=local"

# 3. Сохраняем — миксин автоматически вызовет modify_dn
user.save()

# 4. Проверяем
print(user.dn)  # CN=Ivanov,OU=Dismissed,OU=company,...

Как работает

  1. При загрузке объекта из LDAP (from_db()) сохраняется _original_base_dn
  2. При вызове save() миксин сравнивает текущий base_dn с _original_base_dn
  3. Если изменился — вызывается conn.modify_dn(old_dn, new_rdn, newsuperior=new_base_dn)
  4. После успешного перемещения обновляется self.dn и self._saved_dn
  5. Затем вызывается стандартный super().save() для обновления атрибутов

Логирование

import logging
logging.basicConfig(level=logging.INFO)

user.base_dn = "OU=Dismissed,..."
user.save()

# [INFO] ModifyDnMixin: Moving LDAP object
#   Old DN: cn=User,OU=IT,...
#   New RDN: cn=User
#   New superior: OU=Dismissed,...
#   New DN: cn=User,OU=Dismissed,...
# [INFO] ModifyDnMixin: Successfully moved ...

Модели с ModifyDnMixin

  • LdapUser — перемещение пользователей (отделы, увольнение)
  • LdapGroup — перемещение групп
  • LdapOrganizationalUnit — перемещение OU (редко)

LdapSyncStateMixin — автоматическое управление LdapSyncState

Назначение

Автоматически обновляет LdapSyncState при операциях с LDAP объектами.

Проблема: При изменении DN в LDAP нужно вручную обновлять LdapSyncState.dn.

Решение: Миксин автоматически обновляет/создаёт/удаляет записи LdapSyncState.

Настройка модели

class LdapUser(ModifyDnMixin, LdapSyncStateMixin, LdapModel):
    # Указываем связь с Django моделью
    _sync_model_name = 'employee'  # Для LdapSyncState.model
    _sync_pk_field = 'employee_number'  # Поле LDAP с Django PK
    
    employee_number = CharField(db_column='employeeNumber')
    # ... остальные поля

Автоматические операции

1. CREATE — создаёт LdapSyncState

user = LdapUser.objects.create(
    dn="CN=Ivanov,OU=Users,...",
    employee_number="123",  # Django Employee.pk
    # ... остальные атрибуты
)

# ✅ Автоматически создан LdapSyncState:
# - model='employee'
# - object_id='123'
# - dn='CN=Ivanov,OU=Users,...'
# - last_ldap_modify_ts=now()

2. UPDATE (обычный) — обновляет timestamp

user = LdapUser.objects.get(dn=...)
user.display_name = "New Name"
user.save()

# ✅ Обновлён LdapSyncState:
# - last_ldap_modify_ts=now()

3. UPDATE (с перемещением) — обновляет DN

user = LdapUser.objects.get(dn=old_dn)
user.base_dn = "OU=Dismissed,..."
user.save()

# ✅ Обновлён LdapSyncState:
# - dn='cn=User,OU=Dismissed,...'  # Новый DN!
# - last_ldap_modify_ts=now()

4. DELETE — удаляет LdapSyncState

user.delete()

# ✅ Удалён соответствующий LdapSyncState

Если модель не настроена

Миксин просто не выполняет операции:

class LdapGroup(ModifyDnMixin, LdapSyncStateMixin, LdapModel):
    # Нет _sync_model_name / _sync_pk_field
    pass

group = LdapGroup.objects.create(...)
# [WARNING] LdapSyncStateMixin: No _sync_model_name set, skipping sync state

Вместе: полная интеграция

Пример: увольнение пользователя

from employees.models import Employee
from employees.ldap.orm_models import LdapUser

def dismiss_employee(employee_id: int):
    """Увольняет сотрудника."""
    # 1. Django модель
    employee = Employee.objects.get(pk=employee_id)
    employee.status = 'dismissed'
    employee.save()
    
    # 2. LDAP модель (автоматические обновления!)
    ldap_user = LdapUser.objects.get(employee_number=str(employee_id))
    ldap_user.base_dn = "OU=Dismissed,OU=company,DC=robotail,DC=local"
    ldap_user.save()
    
    # ✅ Произошло автоматически:
    # - ModifyDnMixin: переместил в LDAP (modify_dn)
    # - LdapSyncStateMixin: обновил LdapSyncState.dn
    # - Логирование всех операций

Пример: перевод в другой отдел

def transfer_to_department(employee_id: int, new_dept_name: str):
    """Переводит сотрудника в другой отдел."""
    employee = Employee.objects.get(pk=employee_id)
    new_dept = Department.objects.get(name=new_dept_name)
    
    employee.department = new_dept
    employee.save()
    
    # LDAP перемещение
    ldap_user = LdapUser.objects.get(employee_number=str(employee_id))
    ldap_user.base_dn = f"OU={new_dept_name},OU=Departments,OU=company,DC=robotail,DC=local"
    ldap_user.save()
    
    # ✅ Всё обновлено автоматически!

Ограничения и особенности

ModifyDnMixin

  1. Требует build_rdn() — модель должна реализовать метод build_rdn()
  2. Работает только при изменении base_dn — изменение RDN компонента не поддерживается автоматически
  3. Требует низкоуровневое соединение — использует ldap3.Connection.modify_dn()

LdapSyncStateMixin

  1. Опционально — работает только если установлены _sync_model_name и _sync_pk_field
  2. Требует импорт Employee — использует from employees.models import LdapSyncState
  3. Warning при отсутствии PK — если employee_number пуст, логирует WARNING и пропускает

Интеграция с существующим кодом

Постепенный рефакторинг

Было (LEGACY):

# services/user_service.py
from employees.ldap.utils.dn_utils import _move_to_department

def update_user(self, employee, dto):
    # ... обновление атрибутов через ldap3
    
    # Перемещение в другой отдел
    _move_to_department(old_dn, new_dept_dn)
    
    # Обновление LdapSyncState вручную
    sync_state.dn = new_dn
    sync_state.save()

Стало (ORM + Mixins):

def update_user(self, employee, dto):
    ldap_user = LdapUser.objects.get(employee_number=str(employee.pk))
    
    # Обновление атрибутов
    ldap_user.given_name = dto.first_name
    ldap_user.sn = dto.last_name
    # ... остальные поля
    
    # Перемещение в другой отдел (если изменился)
    if employee.department:
        new_base_dn = f"OU={employee.department.name},OU=Departments,..."
        ldap_user.base_dn = new_base_dn
    
    ldap_user.save()
    
    # ✅ Всё! Миксины сделают остальное

Преимущества

  • -50% кода — нет ручного вызова modify_dn, обновления LdapSyncState
  • Автоматические обновления — меньше ошибок
  • Логирование — встроенное отслеживание операций
  • Единообразие — весь LDAP через ORM

Тестирование

См. примеры в:

  • docs/completed/ldap-orm-create-support.md — тесты CREATE и MOVE
  • backend/employees/ldap/mixins.py — docstrings с примерами
# Запуск тестов
.venv/bin/python manage.py shell -c "..."

Дополнительные ресурсы