Corp/backend/common/ldap_password_mixin.py
lizerk 2ef43e7177 Feature: Add code quality audit tools and safe helpers
- Created CODE_QUALITY_CHECKLIST.md with comprehensive guide
- Added audit_code_patterns.sh for automated code scanning
- Created common/request_utils.py with safe_int(), safe_bool(), etc.
- Fixed unsafe request.data access in notifications/api/views.py
- Updated ldap_password_mixin.py docstring with safe example

Audit found and documented:
- 10 unsafe request.data['key'] accesses
- 8+ unsafe int() conversions without try/except
- Multiple .strip() calls without None checks
2026-03-20 09:06:55 +03:00

110 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Миксин для API endpoint'ов, работающих с паролями в LDAP."""
import logging
from django.conf import settings
from employees.ldap import UserService
from employees.ldap.errors import DirectoryDbError, DirectoryLdapError, DirectoryServiceError
from employees.models import LdapSyncState
logger = logging.getLogger(__name__)
def _is_ldap_enabled():
"""Проверяет, включена ли интеграция с LDAP."""
return getattr(settings, "LDAP_ENABLED", False)
class LdapPasswordMixin:
"""Миксин для endpoint'ов, которые работают с паролями.
Автоматически синхронизирует изменения паролей с LDAP.
Использование:
class ChangePasswordAPIView(LdapPasswordMixin, APIView):
def post(self, request):
user = request.user
# Безопасный доступ:
new_password = request.data.get('new_password')
if not new_password:
return Response({'error': 'Password required'}, 400)
# Меняем пароль в БД
user.set_password(new_password)
user.save()
# Синхронизируем с LDAP (автоматически через миксин)
self.sync_password_to_ldap(user, new_password)
return Response({'ok': True})
"""
def sync_password_to_ldap(self, employee_instance, new_password):
"""Синхронизирует пароль с LDAP для данного Employee.
Args:
employee_instance: инстанс Employee
new_password: новый пароль (plaintext)
Returns:
tuple: (success: bool, error: str or None)
"""
if not _is_ldap_enabled():
return True, None
if not employee_instance.is_ldap_managed:
return True, None
# Проверяем наличие LDAP пользователя
sync_state = LdapSyncState.objects.filter(
model='employee',
object_pk=str(employee_instance.pk)
).first()
if not sync_state or not (sync_state.ldap_dn or sync_state.ldap_guid):
logger.warning(
f"Employee {employee_instance.id} has no LDAP sync state, "
"password not synced to LDAP"
)
return False, "no_ldap_user"
try:
svc = UserService()
svc.update_user(
emp=employee_instance,
changes={'password': new_password},
group_cns=None,
move_to_department_dn=None,
)
logger.info(f"Password synced to LDAP for Employee {employee_instance.id}")
return True, None
except DirectoryLdapError as e:
logger.error(
f"LDAP error during password sync for Employee {employee_instance.id}: {e}",
exc_info=True
)
return False, f"ldap_error: {str(e)}"
except DirectoryServiceError as e:
logger.error(
f"Service error during password sync for Employee {employee_instance.id}: {e}",
exc_info=True
)
return False, f"service_error: {str(e)}"
except DirectoryDbError as e:
logger.error(
f"DB error during password sync for Employee {employee_instance.id}: {e}",
exc_info=True
)
return False, f"db_error: {str(e)}"
except Exception as e:
logger.error(
f"Unexpected error during password sync for Employee {employee_instance.id}: {e}",
exc_info=True
)
return False, f"unexpected_error: {str(e)}"