From aa9fcbe964ad76be27b4a62b5a983124b8dd66e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Mon, 8 Dec 2025 13:14:40 +0300 Subject: [PATCH] fix: handle empty position_id and unwillingToPerform LDAP error Problem: - When clearing employee position (position_id: ''), LDAP sync failed - Error: LDAP modify failed: 'unwillingToPerform' (code 53) - System tried to remove user from position group but LDAP refused Root Causes: 1. Empty string '' not explicitly converted to None 2. unwillingToPerform error not handled (user may not be in group) Solution: 1. Convert position_id: '' to None (no position) at parsing stage 2. Wrap remove_group_members in try/except 3. Ignore unwillingToPerform errors with warning log 4. Add detailed logging for position group operations This allows users to clear positions without LDAP sync failures. --- .../employees/ldap/services/user_service.py | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/backend/employees/ldap/services/user_service.py b/backend/employees/ldap/services/user_service.py index bedbdd01..c25b4eec 100644 --- a/backend/employees/ldap/services/user_service.py +++ b/backend/employees/ldap/services/user_service.py @@ -189,7 +189,10 @@ class UserService: if "position" in changes or "position_id" in changes: pos_in_payload = True val = changes.pop("position", changes.pop("position_id", None)) - if isinstance(val, Position) or val is None: + # Обработка пустой строки как None (снятие должности) + if val == "": + new_pos = None + elif isinstance(val, Position) or val is None: new_pos = val elif isinstance(val, int): new_pos = Position.objects.filter(pk=val).first() @@ -280,6 +283,9 @@ class UserService: # Временное решение - используем методы из DirectoryService # TODO: убрать после создания PositionService (Сессия 8) from ..directory_service import DirectoryService + import logging + logger = logging.getLogger(__name__) + svc = DirectoryService() if old_pos: old_dn = (old_pos.ldap_group_dn or "").strip() @@ -291,7 +297,29 @@ class UserService: ) old_dn = maybe or "" if old_dn: - svc.remove_group_members(conn, old_dn, [current_dn]) + try: + logger.info( + f"Removing user {current_dn} " + f"from position group {old_dn}" + ) + svc.remove_group_members( + conn, old_dn, [current_dn] + ) + except RuntimeError as e: + # Игнорируем unwillingToPerform + # (пользователь не в группе) + error_msg = str(e).lower() + if ( + "unwillingtoperform" in error_msg + or "will_not_perform" in error_msg + ): + logger.warning( + f"LDAP refused to remove user " + f"from group (possibly not a " + f"member): {e}" + ) + else: + raise if new_pos: pos_dn = svc._ensure_position_group(conn, new_pos)