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: НЕТ - обратная совместимость сохранена
19 KiB
План миграции на LDAP ORM
Создан: 19 марта 2026 г.
Статус: 🔄 В работе
Цель: Полный переход от низкоуровневого ldap3 к django-ldapdb ORM + миксины
Обзор текущего состояния
✅ Готово
- ModifyDnMixin — поддержка перемещения объектов
- LdapSyncStateMixin — автоматическое управление LdapSyncState
- Миксины интегрированы в модели (LdapUser, LdapGroup, LdapOrganizationalUnit)
- Протестирована работа CREATE и MOVE через ORM
- Документация по миксинам
❌ LEGACY код требует замены
1. CREATE операции (conn.add)
-
employees/ldap/services/user_service.py_try_create_with_cns()— создание пользователей_create_user_in_ldap()— основной метод создания
-
employees/ldap/services/department_service.py- Создание групп ролей (4 места)
- Создание OU отделов (1 место)
-
employees/ldap/services/group_service.pycreate_group()— создание групп
-
employees/ldap/services/position_service.py- Создание групп позиций
2. MOVE операции (conn.modify_dn)
-
employees/ldap/services/user_service.py_move_user_to_base()— перемещение пользователей
-
employees/ldap/utils/dn_utils.py_move_to_department()— универсальная функция перемещения
-
employees/ldap/services/department_service.py- Коррекция DN при изменении названия отдела
- Коррекция DN групп ролей
3. RENAME операции (conn.modify_dn)
-
employees/ldap/services/user_service.py- Изменение CN при смене ФИО
-
employees/ldap/services/group_service.pyrename_group()— переименование групп
-
employees/ldap/services/department_service.py- Переименование OU отделов
- Переименование групп ролей
Стратегия миграции
Принципы
- Постепенная замена — по одному сервису за раз
- Обратная совместимость — старый код продолжает работать
- 100% покрытие тестами — каждое изменение тестируется
- Rollback plan — возможность откатиться назад
Приоритеты
- 🟢 P0 (критично): UserService — основная функциональность
- 🟡 P1 (важно): DepartmentService, GroupService
- 🟠 P2 (среднее): PositionService
- 🔵 P3 (низкое): Утилиты, вспомогательные функции
Этапы миграции
ФАЗА 1: UserService (P0) — 3-4 дня
1.1. CREATE пользователей
Цель: Заменить conn.add() на LdapUser.objects.create()
Файлы:
employees/ldap/services/user_service.py
Изменения:
# Было (LEGACY):
def _try_create_with_cns(self, conn, dto, sam, upn, base_dn, object_classes, cns):
for cn_txt in cns:
dn_try = f"CN={cn_txt},{base_dn}"
attrs = self._build_user_attrs(dto, sam, upn, cn_txt)
if conn.add(dn_try, object_classes, attrs):
return dn_try
return None
# Станет (ORM):
def _try_create_with_cns_orm(self, dto, sam, upn, base_dn, cns):
"""Создание через ORM с обработкой коллизий."""
from ldap import ALREADY_EXISTS
from employees.ldap.orm_models import LdapUser
for cn_txt in cns:
try:
user = LdapUser.objects.create(
dn=f"CN={cn_txt},{base_dn}",
cn=cn_txt,
sam_account_name=sam,
user_principal_name=upn,
given_name=dto.first_name,
sn=dto.last_name or ".",
display_name=f"{dto.first_name} {dto.last_name or ''}".strip(),
mail=dto.email,
user_account_control=512, # Normal account
employee_number=str(dto.employee_id) if dto.employee_id else "",
# ... остальные поля из dto
)
return user.dn
except ALREADY_EXISTS:
continue
return None
Преимущества:
- Убираем
UserMapperService._build_user_attrs()— атрибуты задаются напрямую - Автоматическая валидация полей
- LdapSyncStateMixin создаст LdapSyncState автоматически
- Меньше кода (~40% сокращение)
Тестирование:
- Unit тесты для
_try_create_with_cns_orm() - Integration тесты: создание пользователя с коллизиями CN
- Проверка LdapSyncState после создания
Риски:
- 🟡 Другие поля могут быть не учтены
- 🟢 Rollback: оставить старый метод как
_try_create_with_cns_legacy()
1.2. MOVE пользователей (увольнение, перевод)
Цель: Заменить conn.modify_dn() на user.base_dn = ...; user.save()
Файлы:
employees/ldap/services/user_service.pyemployees/ldap/utils/dn_utils.py
Изменения:
# Было (LEGACY):
def _move_user_to_base(self, conn, user_dn, base_dn):
rdn = user_dn.split(",", 1)[0]
ok = conn.modify_dn(user_dn, rdn, new_superior=base_dn)
if not ok:
raise RuntimeError(f"LDAP move failed: {conn.result}")
return f"{rdn},{base_dn}"
# Станет (ORM):
def _move_user_to_base_orm(self, user_dn, base_dn):
"""Перемещение через ModifyDnMixin."""
from employees.ldap.orm_models import LdapUser
user = LdapUser.objects.get(dn=user_dn)
user.base_dn = base_dn
user.save() # ModifyDnMixin автоматически вызовет modify_dn
return user.dn # Обновлённый DN
Преимущества:
- ModifyDnMixin автоматически обновляет LdapSyncState.dn
- Логирование операций из коробки
- Меньше ошибок (нет ручного формирования DN)
Тестирование:
- Unit тесты для
_move_user_to_base_orm() - Integration тесты: перемещение в Dismissed, в отдел
- Проверка обновления LdapSyncState.dn
Риски:
- 🟢 Низкий — миксин уже протестирован
1.3. RENAME пользователей (смена ФИО)
Цель: Сохранить conn.modify_dn() для изменения RDN (cn)
Обоснование:
- Изменение RDN (cn=...) != перемещение (newsuperior)
- ModifyDnMixin работает только с
base_dn - Для RDN нужен отдельный подход
Решение:
# Оставляем LEGACY для изменения CN:
def _update_cn_if_needed(self, conn, dn, new_cn):
"""Изменение CN через modify_dn (без newsuperior)."""
current_rdn = dn.split(",", 1)[0]
new_rdn = f"CN={new_cn}"
if current_rdn != new_rdn:
ok = conn.modify_dn(dn, new_rdn)
if not ok:
raise RuntimeError(f"Failed to rename: {conn.result}")
# Обновляем LdapSyncState вручную
base = dn.split(",", 1)[1]
new_dn = f"{new_rdn},{base}"
self._update_sync_state_dn(old_dn=dn, new_dn=new_dn)
return new_dn
return dn
Альтернатива (будущее):
- Создать
RenameRdnMixinдля изменения RDN - Но это P3 приоритет
Тестирование:
- Unit тесты сохранения старой логики
- Проверка обновления LdapSyncState
ФАЗА 2: DepartmentService (P1) — 2-3 дня
2.1. CREATE OU отделов
Цель: conn.add() → LdapOrganizationalUnit.objects.create()
Изменения:
# Было:
ok = conn.add(dn, ["top", "organizationalUnit"])
# Станет:
from employees.ldap.orm_models import LdapOrganizationalUnit
ou = LdapOrganizationalUnit.objects.create(
dn=dn,
ou=dept_name,
description=f"Отдел {dept_name}",
managed_by=manager_dn if manager_dn else "",
)
Тестирование:
- Создание OU через ORM
- Проверка атрибутов (description, managedBy)
2.2. CREATE групп ролей
Цель: conn.add() → LdapGroup.objects.create()
Изменения:
# Было:
attrs = {
"sAMAccountName": sam,
"description": desc,
"member": members,
}
ok = conn.add(group_dn, ["top", "group"], attrs)
# Станет:
from employees.ldap.orm_models import LdapGroup
group = LdapGroup.objects.create(
dn=group_dn,
cn=cn,
sam_account_name=sam,
description=desc,
member=members if members else [],
)
Тестирование:
- Создание групп ролей через ORM
- Проверка членства (member list)
2.3. RENAME OU при изменении названия отдела
Цель: conn.modify_dn() → ou.base_dn = ...; ou.save() (для MOVE)
ВАЖНО: Для изменения самого OU (не перемещения) нужен modify_dn(new_rdn=...)
Решение:
- Оставить
conn.modify_dn()для изменения RDN OU - Использовать ModifyDnMixin только если планируется перемещение OU (редко)
Тестирование:
- Переименование OU (IT → Engineering)
- Проверка обновления всех вложенных DN
ФАЗА 3: GroupService (P1) — 1-2 дня
3.1. CREATE глобальных групп
Цель: conn.add() → LdapGroup.objects.create()
Аналогично DepartmentService 2.2
3.2. RENAME групп
Цель: Оставить conn.modify_dn() для изменения CN
Аналогично UserService 1.3
ФАЗА 4: PositionService (P2) — 1 день
4.1. CREATE групп позиций
Цель: conn.add() → LdapGroup.objects.create()
Аналогично DepartmentService 2.2
ФАЗА 5: Утилиты и рефакторинг (P3) — 2-3 дня
5.1. Создать RenameRdnMixin
Цель: Унифицировать изменение RDN через миксин
class RenameRdnMixin:
"""Миксин для изменения RDN (cn=..., ou=...) без перемещения."""
def rename_rdn(self, new_rdn_value):
"""Изменяет RDN без перемещения между контейнерами.
Example:
user.rename_rdn("Ivanov I 2") # cn=Ivanov I → cn=Ivanov I 2
"""
from .infrastructure.connections import _ldap
old_dn = self.dn
rdn_attr = self.build_rdn().split("=")[0] # "cn" или "ou"
new_rdn = f"{rdn_attr}={new_rdn_value}"
with _ldap() as conn:
success = conn.modify_dn(old_dn, new_rdn)
if not success:
raise RuntimeError(f"Failed to rename: {conn.result}")
# Обновляем self.dn
base = old_dn.split(",", 1)[1]
self.dn = f"{new_rdn},{base}"
Интеграция:
class LdapUser(ModifyDnMixin, RenameRdnMixin, LdapSyncStateMixin, LdapModel):
...
# Использование:
user = LdapUser.objects.get(dn=...)
user.rename_rdn("Petrov P") # Изменение CN
user.save() # Обновление остальных атрибутов
5.2. Удалить LEGACY код
Цель: Удалить старые методы после полной миграции
Удалить:
UserMapperService._build_user_attrs()— заменено на прямое создание через ORMdn_utils._move_to_department()— заменено на ModifyDnMixin- Все методы с
_legacyсуффиксом
Проверить зависимости:
# Найти все использования LEGACY методов
grep -r "_build_user_attrs" backend/
grep -r "_move_to_department" backend/
5.3. Обновить документацию
Цель: Убрать упоминания о LEGACY, обновить примеры
Файлы:
employees/ldap/orm_models.py— убрать секцию LEGACYdocs/guides/ldap-orm-mixins-usage.md— добавить RenameRdnMixindocs/completed/ldap-orm-create-support.md— пометить как завершённое
Тестирование
Unit тесты
- Создание объектов через ORM (все модели)
- Перемещение через ModifyDnMixin
- Обработка коллизий CN
- Обновление LdapSyncState автоматически
Integration тесты
- Полный цикл: создание → перемещение → обновление → удаление
- Создание пользователя → перевод в отдел → увольнение
- Создание отдела → назначение менеджера → переименование
- Создание группы → добавление членов → переименование
Regression тесты
- Существующие тесты проходят без изменений
- Функциональность не сломана
Performance тесты
- ORM не медленнее ldap3 (допустимо +10%)
- Batch операции работают эффективно
Риски и митигация
🔴 Высокие риски
-
Потеря данных при ошибке в миграции
- Митигация: резервное копирование LDAP перед каждой фазой
- Rollback: восстановление из бэкапа
-
Несовместимость с существующими интеграциями
- Митигация: тестирование на staging
- Rollback: feature flag для переключения ORM/LEGACY
🟡 Средние риски
-
Производительность ORM ниже ldap3
- Митигация: профилирование и оптимизация
- Альтернатива: batch операции через bulk_create
-
Неучтённые edge cases
- Митигация: расширенное тестирование
- Rollback: постепенная миграция (по одному сервису)
🟢 Низкие риски
- Изменение поведения при обработке ошибок
- Митигация: обработка тех же exception (ALREADY_EXISTS, etc.)
Чеклист выполнения
Подготовка
- Создать feature branch
feature/ldap-orm-migration - Настроить CI/CD для автоматического тестирования
- Создать резервную копию LDAP
ФАЗА 1: UserService
- Реализовать
_try_create_with_cns_orm() - Написать unit тесты CREATE
- Реализовать
_move_user_to_base_orm() - Написать unit тесты MOVE
- Integration тесты UserService
- Code review
- Merge в develop
ФАЗА 2: DepartmentService
- Реализовать CREATE OU через ORM
- Реализовать CREATE групп через ORM
- Unit тесты
- Integration тесты
- Code review
- Merge в develop
ФАЗА 3: GroupService
- Реализовать CREATE через ORM
- Unit тесты
- Integration тесты
- Code review
- Merge в develop
ФАЗА 4: PositionService
- Реализовать CREATE через ORM
- Unit тесты
- Code review
- Merge в develop
ФАЗА 5: Финализация
- Создать RenameRdnMixin (опционально)
- Удалить LEGACY код
- Обновить документацию
- Final regression тесты
- Production deployment
Оценка времени
| Фаза | Описание | Оценка | Зависимости |
|---|---|---|---|
| 1 | UserService (CREATE + MOVE) | 3-4 дня | - |
| 2 | DepartmentService | 2-3 дня | Фаза 1 |
| 3 | GroupService | 1-2 дня | Фаза 1 |
| 4 | PositionService | 1 день | Фаза 1 |
| 5 | Рефакторинг + документация | 2-3 дня | Фазы 1-4 |
| ИТОГО | Полная миграция | 9-13 дней | - |
С учётом тестирования и ревью: ~15-20 рабочих дней (3-4 недели)
Success Criteria
Функциональные
- ✅ Все операции CREATE выполняются через ORM
- ✅ Все операции MOVE выполняются через ModifyDnMixin
- ✅ LdapSyncState обновляется автоматически
- ✅ Обработка коллизий CN работает корректно
Технические
- ✅ 100% покрытие unit тестами новых методов
- ✅ Integration тесты проходят успешно
- ✅ Regression тесты не выявляют проблем
- ✅ Performance не деградировала (±10%)
Качество кода
- ✅ Код соответствует PEP 8
- ✅ Все методы задокументированы (docstrings)
- ✅ Нет дублирования кода
- ✅ Code review пройден
Документация
- ✅ Обновлена документация ORM моделей
- ✅ Обновлены гайды по миксинам
- ✅ Создана migration guide для команды
Следующие шаги
- Обсудить план с командой — получить feedback
- Установить приоритеты — согласовать порядок фаз
- Начать ФАЗУ 1 — UserService как самый критичный компонент
- Еженедельные ревью — отслеживать прогресс
Контакты и ответственные
- Tech Lead: @username
- Backend разработчик (LDAP): @username
- QA Engineer: @username
- DevOps (backup/rollback): @username
Статус обновлён: 19 марта 2026 г.
Следующий ревью: TBD