198 lines
6.7 KiB
Python
198 lines
6.7 KiB
Python
"""Django signals для синхронизации Position с LDAP.
|
||
|
||
Файл: employees/signals/ldap/position.py
|
||
Отвечает за синхронизацию POS-групп должностей с Active Directory
|
||
при изменении Position.groups.
|
||
"""
|
||
|
||
import logging
|
||
|
||
from django.conf import settings
|
||
from django.db.models.signals import m2m_changed, post_delete, post_save
|
||
from django.dispatch import receiver
|
||
|
||
from employees.ldap import PositionService
|
||
from employees.ldap.errors import (
|
||
DirectoryDbError,
|
||
DirectoryLdapError,
|
||
DirectoryServiceError,
|
||
)
|
||
from employees.models import Position
|
||
from employees.signals.ldap._queue import _enqueue
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def _is_ldap_enabled():
|
||
"""Проверяет, включена ли интеграция с LDAP."""
|
||
return getattr(settings, "LDAP_ENABLED", False)
|
||
|
||
|
||
@receiver(post_save, sender=Position)
|
||
def sync_position_to_ldap_on_save(sender, instance, created, **kwargs):
|
||
"""Создает/обновляет POS-группу должности в LDAP при сохранении Position.
|
||
|
||
При создании: создает POS-группу (CN=POS_<name>)
|
||
При обновлении: переименовывает POS-группу при изменении имени
|
||
|
||
Использует временный атрибут _skip_ldap_sync для отключения синхронизации.
|
||
"""
|
||
if not _is_ldap_enabled():
|
||
return
|
||
|
||
if getattr(instance, "_skip_ldap_sync", False):
|
||
return
|
||
|
||
try:
|
||
svc = PositionService()
|
||
|
||
# reconcile_position гарантирует наличие POS-группы
|
||
# и обновляет её при необходимости
|
||
# Также синхронизирует вложенность в целевые группы и участников
|
||
svc.reconcile_position(instance)
|
||
|
||
logger.info(
|
||
"LDAP position synchronized",
|
||
extra={
|
||
"event": "ldap.position.synced",
|
||
"position_id": instance.pk,
|
||
"object_created": created,
|
||
},
|
||
)
|
||
except (DirectoryLdapError, DirectoryServiceError, DirectoryDbError) as e:
|
||
logger.exception(
|
||
"LDAP position sync failed",
|
||
extra={
|
||
"event": "ldap.position.sync_failed",
|
||
"position_id": instance.pk,
|
||
"object_created": created,
|
||
"error_type": type(e).__name__,
|
||
},
|
||
)
|
||
_enqueue(
|
||
"position_save",
|
||
"position",
|
||
instance.pk,
|
||
{
|
||
"object_pk": str(instance.pk),
|
||
},
|
||
)
|
||
except Exception as e:
|
||
logger.exception(
|
||
"Unexpected LDAP position sync failure",
|
||
extra={
|
||
"event": "ldap.position.sync_unexpected_failure",
|
||
"position_id": instance.pk,
|
||
"object_created": created,
|
||
"error_type": type(e).__name__,
|
||
},
|
||
)
|
||
|
||
|
||
@receiver(post_delete, sender=Position)
|
||
def sync_position_to_ldap_on_delete(sender, instance, **kwargs):
|
||
"""Удаляет POS-группу должности из LDAP при удалении Position."""
|
||
if not _is_ldap_enabled():
|
||
return
|
||
|
||
try:
|
||
svc = PositionService()
|
||
svc.delete_position_group(instance)
|
||
logger.info(
|
||
"LDAP position group deleted",
|
||
extra={"event": "ldap.position.deleted", "position_id": instance.pk},
|
||
)
|
||
except (DirectoryLdapError, DirectoryServiceError, DirectoryDbError) as e:
|
||
logger.exception(
|
||
"LDAP position deletion failed",
|
||
extra={
|
||
"event": "ldap.position.delete_failed",
|
||
"position_id": instance.pk,
|
||
"error_type": type(e).__name__,
|
||
},
|
||
)
|
||
_enqueue(
|
||
"position_delete",
|
||
"position",
|
||
instance.pk,
|
||
{
|
||
"object_pk": str(instance.pk),
|
||
"name": instance.name,
|
||
},
|
||
)
|
||
except Exception as e:
|
||
logger.exception(
|
||
"Unexpected LDAP position deletion failure",
|
||
extra={
|
||
"event": "ldap.position.delete_unexpected_failure",
|
||
"position_id": instance.pk,
|
||
"error_type": type(e).__name__,
|
||
},
|
||
)
|
||
|
||
|
||
@receiver(m2m_changed, sender=Position.groups.through)
|
||
def sync_position_groups_to_ldap(sender, instance, action, **kwargs):
|
||
"""Синхронизирует изменения Position.groups с LDAP.
|
||
|
||
При изменении групп должности (add/remove/clear/set):
|
||
- Обновляет вложенность POS-группы в целевые AD-группы
|
||
- POS-группа добавляется в AD-группы, соответствующие Position.groups
|
||
- POS-группа удаляется из AD-групп, не указанных в Position.groups
|
||
|
||
Реагирует на post_add, post_remove, post_clear
|
||
для синхронизации вложенности.
|
||
"""
|
||
if not _is_ldap_enabled():
|
||
return
|
||
|
||
if action not in ["post_add", "post_remove", "post_clear"]:
|
||
return
|
||
|
||
if getattr(instance, "_skip_ldap_sync", False):
|
||
return
|
||
|
||
try:
|
||
svc = PositionService()
|
||
|
||
# reconcile_position обновит вложенность POS-группы в целевые AD-группы
|
||
# на основе текущего состояния Position.groups
|
||
svc.reconcile_position(instance)
|
||
|
||
logger.info(
|
||
"LDAP position groups synchronized",
|
||
extra={
|
||
"event": "ldap.position.groups_synced",
|
||
"position_id": instance.pk,
|
||
"action": action,
|
||
},
|
||
)
|
||
except (DirectoryLdapError, DirectoryServiceError, DirectoryDbError) as e:
|
||
logger.exception(
|
||
"LDAP position groups sync failed",
|
||
extra={
|
||
"event": "ldap.position.groups_sync_failed",
|
||
"position_id": instance.pk,
|
||
"action": action,
|
||
"error_type": type(e).__name__,
|
||
},
|
||
)
|
||
_enqueue(
|
||
"position_save",
|
||
"position",
|
||
instance.pk,
|
||
{
|
||
"object_pk": str(instance.pk),
|
||
},
|
||
)
|
||
except Exception as e:
|
||
logger.exception(
|
||
"Unexpected LDAP position groups sync failure",
|
||
extra={
|
||
"event": "ldap.position.groups_sync_unexpected_failure",
|
||
"position_id": instance.pk,
|
||
"action": action,
|
||
"error_type": type(e).__name__,
|
||
},
|
||
)
|