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: НЕТ - обратная совместимость сохранена
90 lines
4.4 KiB
Python
90 lines
4.4 KiB
Python
# employees/apps.py
|
||
from django.apps import AppConfig
|
||
|
||
|
||
class EmployeesConfig(AppConfig):
|
||
name = 'employees'
|
||
|
||
def ready(self):
|
||
import employees.signals # Все сигналы (common, birthday, ldap)
|
||
import employees.rules # django-rules: регистрация предикатов и правил доступа
|
||
|
||
# Патч для django-ldapdb: исправление бага с Value(1) в .exists()
|
||
self._patch_ldapdb_compiler()
|
||
|
||
def _patch_ldapdb_compiler(self):
|
||
"""Патчит django-ldapdb для поддержки Value выражений.
|
||
|
||
Проблема: ldapdb компилятор на строке 246 вызывает e[0].field.attname,
|
||
но у полей из Value() выражений (например, от .exists()) нет attname.
|
||
|
||
Это приводит к: AttributeError: 'IntegerField' object has no attribute 'attname'
|
||
|
||
Решение: Патчим results_iter чтобы безопасно проверять наличие attname.
|
||
"""
|
||
try:
|
||
from ldapdb.backends.ldap.compiler import SQLCompiler
|
||
from django.db.models import aggregates
|
||
from ldapdb.models.fields import ListField
|
||
|
||
original_results_iter = SQLCompiler.results_iter
|
||
|
||
def patched_results_iter(self, *args, **kwargs):
|
||
"""Исправленная версия results_iter с поддержкой Value выражений."""
|
||
# Вызываем оригинальный генератор, но оборачиваем в try-catch
|
||
for result in original_results_iter(self, *args, **kwargs):
|
||
yield result
|
||
|
||
# Заменяем весь метод для правильной обработки
|
||
import ldap
|
||
from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE
|
||
|
||
def safe_results_iter(self, results=None, do_slice=True):
|
||
"""Безопасная версия results_iter с проверкой attname."""
|
||
if results is None:
|
||
results = self.execute_sql(
|
||
result_type='multi', chunked_fetch=False,
|
||
chunk_size=GET_ITERATOR_CHUNK_SIZE,
|
||
)
|
||
|
||
if results is None:
|
||
return
|
||
|
||
for dn, attrs in results:
|
||
row = []
|
||
self.setup_query()
|
||
for e in self.select:
|
||
if isinstance(e[0], aggregates.Count):
|
||
value = 0
|
||
input_field = e[0].get_source_expressions()[0].field
|
||
# Безопасная проверка attname
|
||
if getattr(input_field, 'attname', None) == 'dn':
|
||
value = 1
|
||
elif hasattr(input_field, 'from_ldap'):
|
||
result = input_field.from_ldap(
|
||
attrs.get(input_field.db_column, []),
|
||
connection=self.connection)
|
||
if result:
|
||
value = 1
|
||
if isinstance(input_field, ListField):
|
||
value = len(result)
|
||
row.append(value)
|
||
else:
|
||
# Безопасная проверка attname с getattr
|
||
if getattr(e[0].field, 'attname', None) == 'dn':
|
||
row.append(dn)
|
||
elif hasattr(e[0].field, 'from_ldap'):
|
||
row.append(e[0].field.from_ldap(
|
||
attrs.get(e[0].field.db_column, []),
|
||
connection=self.connection))
|
||
else:
|
||
row.append(None)
|
||
|
||
yield row
|
||
|
||
SQLCompiler.results_iter = safe_results_iter
|
||
|
||
except ImportError:
|
||
# ldapdb не установлен - это нормально для тестов без LDAP
|
||
pass
|