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: НЕТ - обратная совместимость сохранена
286 lines
9.4 KiB
Markdown
286 lines
9.4 KiB
Markdown
# Использование 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=...)`.
|
||
|
||
### Использование
|
||
|
||
```python
|
||
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()` для обновления атрибутов
|
||
|
||
### Логирование
|
||
|
||
```python
|
||
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`.
|
||
|
||
### Настройка модели
|
||
|
||
```python
|
||
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
|
||
|
||
```python
|
||
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
|
||
|
||
```python
|
||
user = LdapUser.objects.get(dn=...)
|
||
user.display_name = "New Name"
|
||
user.save()
|
||
|
||
# ✅ Обновлён LdapSyncState:
|
||
# - last_ldap_modify_ts=now()
|
||
```
|
||
|
||
#### 3. UPDATE (с перемещением) — обновляет DN
|
||
|
||
```python
|
||
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
|
||
|
||
```python
|
||
user.delete()
|
||
|
||
# ✅ Удалён соответствующий LdapSyncState
|
||
```
|
||
|
||
### Если модель не настроена
|
||
|
||
Миксин просто не выполняет операции:
|
||
|
||
```python
|
||
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
|
||
```
|
||
|
||
---
|
||
|
||
## Вместе: полная интеграция
|
||
|
||
### Пример: увольнение пользователя
|
||
|
||
```python
|
||
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
|
||
# - Логирование всех операций
|
||
```
|
||
|
||
### Пример: перевод в другой отдел
|
||
|
||
```python
|
||
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):
|
||
|
||
```python
|
||
# 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):
|
||
|
||
```python
|
||
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 с примерами
|
||
|
||
```python
|
||
# Запуск тестов
|
||
.venv/bin/python manage.py shell -c "..."
|
||
```
|
||
|
||
---
|
||
|
||
## Дополнительные ресурсы
|
||
|
||
- [django-ldapdb документация](https://github.com/django-ldapdb/django-ldapdb)
|
||
- [RFC 4511 — LDAP Protocol](https://www.rfc-editor.org/rfc/rfc4511.html)
|
||
- [Active Directory DN структура](https://docs.microsoft.com/en-us/windows/win32/ad/naming-properties)
|