- 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
110 lines
4.0 KiB
Python
110 lines
4.0 KiB
Python
"""Миксин для 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)}"
|