66 Commits

Author SHA1 Message Date
4f6244ab1f Fix CORP runtime errors and harden deployment and media access
Some checks failed
Deploy production / deploy (push) Failing after 2s
2026-09-10 16:40:08 +03:00
lizerk
dac0234007 Enforce repository-wide code quality 2026-09-07 19:43:48 +03:00
lizerk
cca52d70d6 Refactor project structure and remove obsolete code 2026-08-15 11:16:50 +03:00
lizerk
aaa1da663c refactor: unify observability and realtime updates 2026-08-14 22:49:20 +03:00
lizerk
78f19e27af Remove obsolete files and simplify project structure 2026-08-14 16:35:21 +03:00
lizerk
df2377431d feat: expand documents tasks and procurement workflows 2026-07-17 14:09:06 +03:00
lizerk
32500e3037 chore: checkpoint current project changes 2026-06-20 10:47:00 +03:00
lizerk
b4de4aa449 chore: prepare EUSRR for public portfolio 2026-06-17 16:21:38 +03:00
lizerk
2d00699831 fix(ldap): nest department role groups into department group 2026-04-14 01:07:13 +03:00
lizerk
5ab638892b chore: checkpoint workspace changes 2026-04-13 16:00:53 +03:00
lizerk
b98a82176c test(ldap): add live integration coverage and fix sync paths 2026-04-12 03:51:52 +03:00
lizerk
81b8d29b38 Refactor department LDAP sync around canonical service flows 2026-04-10 20:54:09 +03:00
lizerk
6ee32111db chore: configure vscode flake8 and commit lint cleanup 2026-03-31 19:17:56 +03:00
lizerk
2815efe069 chore: finalize backend lint cleanup 2026-03-31 17:53:29 +03:00
lizerk
9edb44502a Harden backend warnings and lint policy 2026-03-31 17:19:59 +03:00
lizerk
3f59f8ebb9 Add backend OpenAPI docs and clean test suite 2026-03-31 17:05:31 +03:00
lizerk
540e295ef4 feat: Fix LDAP DN bugs + add password management in admin
BREAKING CHANGES:
- Department.head автоматически очищается при увольнении руководителя
- LDAP managedBy атрибут очищается при увольнении

Fixes:
- DN не перемещался при восстановлении сотрудника (Dismissed → Users)
- DN не перемещался при увольнении сотрудника без отделов
- Ошибка при увольнении из множественных отделов (двойной modify_dn)
- Неправильные URL patterns для LDAP моделей в админке
- Двойное кодирование DN в редиректе смены пароля

Features:
- Добавлен метод _ensure_ldap_dn_location для гарантированного перемещения DN
- Добавлена проверка already_there в sync_department_member_to_ldap
- Управление паролями LDAP пользователей через админку:
  * Кнопка смены пароля в карточке пользователя
  * Массовое действие смены пароля
  * Автосинхронизация с Django password hash
- Автоочистка Department.head и LDAP managedBy при увольнении руководителя
- Добавлены методы set_password() и change_password() в LdapUser

Changed:
- Переписан сигнал sync_department_member_to_ldap с реальными методами
- Исправлены URL patterns: ldap_* → employees_*
2026-03-23 20:32:39 +03:00
lizerk
1a3d533c1d админка + 2026-03-22 13:55:26 +03:00
lizerk
4c5ea98743 админка+ 2026-03-20 14:44:41 +03:00
lizerk
55d0bcf906 Feature: Add validation warnings for missing fields in sync actions
Both LDAP→Django and Django→LDAP sync actions now check for missing
required fields (name, email, employeeNumber) and display warnings
before syncing. Shows up to 10 warnings per action.
2026-03-20 02:35:29 +03:00
lizerk
7a770165f9 Fix: Correct get_action_choices signature (use BLANK_CHOICE_DASH) 2026-03-20 02:31:11 +03:00
lizerk
130dafc5a2 Fix: Replace delete_selected with LDAP-safe custom action
Django's delete_selected passes queryset with dn__in filter that
ldapdb doesn't support. Custom delete_selected_ldap reads DNs from
POST and deletes objects one by one. Also hides standard delete_selected
from action choices.
2026-03-20 02:20:42 +03:00
lizerk
6050cd9d26 Fix: Handle ldapdb unsupported dn__in lookup in admin delete
- Override get_deleted_objects() in LdapUserAdmin to avoid dn__in
  queryset that ldapdb doesn't support
- Also fix phone_number UNIQUE constraint in LDAP auth backend
2026-03-20 02:18:39 +03:00
lizerk
63a96a0122 Fix: Render thumbnailPhoto as image preview in LdapUser admin
Binary thumbnail_photo data caused 500 error when Django admin tried
to render it as a text input. Now displayed as base64 inline image
preview in a readonly field.
2026-03-20 02:03:09 +03:00
lizerk
efc649aeae Feat: Implement department move and position group sync in UserService
- Department move: set ldap_user.base_dn via ModifyDnMixin (replaces DirectoryService)
- Position sync: assign/unassign POS-groups via PositionService on position change
- DB update: position_id saved to Employee in atomic block
- Best-effort: position sync errors logged but don't interrupt update
2026-03-20 01:45:25 +03:00
lizerk
0ba594d317 Style: Fix PEP8 whitespace and line-length in orm_models.py 2026-03-20 01:37:05 +03:00
lizerk
9bc4d70a27 Cleanup: Remove dead position sync code from user_service
- Removed 30 lines of commented-out DirectoryService code
- Removed unused Position import
- Removed unused old_pos/new_pos/pos_in_payload variables
- Fixed stale comment about displayName auto-update in save()
- Position sync remains as a 3-line TODO comment
2026-03-20 01:26:04 +03:00
lizerk
999ecaaadf Fix: Handle RDN rename in ModifyDnMixin.save()
AD forbids modifying RDN (CN/OU) via modify_s - only via rename.
ModifyDnMixin now:
1. Tracks _original_rdn_value (cn/ou) from __init__/from_db
2. On save(), if RDN changed: reverts to old value → save() → modify_dn
3. This fixes NOT_ALLOWED_ON_RDN for both admin and API edits

Simplified user_service: just set ldap_user.cn = new_cn and call save().
The mixin handles the rest automatically.
2026-03-20 01:21:03 +03:00
lizerk
afd66662ce Fix: Don't modify CN via save(), use modify_dn only
AD forbids modifying RDN (CN) through modify_s - must use rename.
- Removed LdapUser.save() override that synced cn→displayName
  (this caused cn to be in modlist → NOT_ALLOWED_ON_RDN error)
- user_service now sets displayName explicitly instead of cn
- CN is only changed via conn.modify_dn() after save()
- Flow: set displayName + other attrs → save() → modify_dn(cn)
2026-03-20 01:11:44 +03:00
lizerk
7b7e071df1 Fix: Auto-sync displayName with cn in LdapUser.save()
- Added save() override in LdapUser to auto-update displayName from cn
- Fixed user_service to set ldap_user.cn BEFORE save() (not after)
- This ensures displayName is synced when givenName/sn change
- Previously displayName wasn't updated because cn was set after save()
- Now: set cn → save() → displayName auto-synced → modify_dn()
2026-03-20 01:09:24 +03:00
lizerk
11e0714411 Fix: Override build_rdn() to fix ldapdb 1.5.1 bug
ldapdb's build_rdn() checks field.primary_key AND field.db_column,
but the 'dn' field has primary_key=True and db_column=None,
while 'cn'/'ou' fields have db_column set but primary_key=False.
No field satisfies both conditions → save() always raises
'Could not build Distinguished Name'.

Fix: ModifyDnMixin.build_rdn() extracts RDN from existing DN
for loaded objects, and builds from rdn_attributes for new ones.
2026-03-20 00:55:56 +03:00
lizerk
a3bd9226a5 Fix: Refactor LDAP field handling to use get_ldap_str()
- Moved get_ldap_str() helper to ldap_utils.py for reuse
- Updated user_service.py to use shared helper from utils
- Updated admin.py to safely extract string values from LDAP fields
- LDAP fields can be lists, strings, or None - helper handles all cases
- Fixes sync_from_ldap_to_django and compare_ldap_django actions
- Prevents 'list' object errors when comparing or assigning LDAP fields
2026-03-20 00:40:51 +03:00
lizerk
be2e899046 Fix: Handle LDAP fields as lists in user_service.py
- Added _get_ldap_str() helper to safely extract string values from LDAP fields
- LDAP fields (given_name, sn, etc.) can be returned as lists, not strings
- Fixes AttributeError: 'list' object has no attribute 'strip'
- Used helper in CN generation code (lines 761-764)
2026-03-20 00:38:35 +03:00
lizerk
f8762894cd Fix: Remove deprecated DirectoryService imports
- DirectoryService module no longer exists
- Commented out position/department sync code that used DirectoryService
- Marked with TODO for future refactoring
- Fixes ModuleNotFoundError during LDAP sync operations
2026-03-20 00:35:00 +03:00
lizerk
36c5323e0e Fix employeeNumber update: use direct LDAP modify instead of ORM save
- Replaced ORM .save() with direct ldap3.Connection.modify()
- ORM save() tries to rebuild DN which fails without proper cn attribute
- Direct modify only changes the employeeNumber attribute
- Fixes Exception: Could not build Distinguished Name
2026-03-20 00:22:39 +03:00
lizerk
4cf989e5b5 Fix _UtcCompat: add missing normalize() method
- ldapdb calls timezone.utc.normalize() for datetime field conversion
- Added normalize() method that works the same as localize() for UTC
- Fixes AttributeError: '_UtcCompat' object has no attribute 'normalize'
2026-03-20 00:21:04 +03:00
lizerk
ca1deac82b Fix LDAP user creation: handle existing Employee with same email
- Check if Employee with same email exists before creating new one
- Update existing Employee and link it to LDAP instead of creating duplicate
- Prevents UNIQUE constraint failed error on email field
- Sets is_ldap_managed=True and updates user data from LDAP
2026-03-20 00:16:29 +03:00
lizerk
175796ff78 Fix LDAP admin actions: resolve dn__in unsupported lookup
- Added _resolve_ldap_users() helper to fetch LDAP objects by DN individually
- ldapdb does not support dn__in lookup used by Django admin queryset
- All 3 actions now use _resolve_ldap_users instead of iterating queryset directly
2026-03-20 00:12:12 +03:00
lizerk
14c396d483 Fix timezone compatibility: Properly implement _UtcCompat as tzinfo subclass
- Made _UtcCompat inherit from datetime.tzinfo instead of plain class
- Implemented required tzinfo methods: utcoffset, tzname, dst
- Fixes TypeError: tzinfo argument must be None or of a tzinfo subclass
- Resolves admin panel crashes when viewing LDAP users with timestamps
2026-03-20 00:09:57 +03:00
lizerk
f769c69b6b Fix binary data decoding error: Remove objectGUID field from LDAP models
- Removed objectGUID field from LdapUser, LdapGroup, and LdapOrganizationalUnit models
- objectGUID contains binary data that causes UnicodeDecodeError when decoded as UTF-8
- Field is not critical for core functionality
- Updated admin.py to not use object_guid attribute
2026-03-20 00:04:31 +03:00
lizerk
98e98289bf Fix LDAP synchronization issues, improve logging, and resolve binary decoding errors 2026-03-19 23:50:27 +03:00
lizerk
06931f3706 Audit and fix LDAP module: imports, docstrings, tests 2026-03-19 23:12:22 +03:00
lizerk
75662ec7ea feat(ldap): Полная миграция на django-ldapdb ORM с миксинами и Django Admin
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: НЕТ - обратная совместимость сохранена
2026-03-19 22:33:06 +03:00
lizerk
f15e8aca3f feat(ldap): add LDAP sync retry queue with Celery
- Add LdapSyncQueue model for deferred LDAP operations
- Add Celery tasks: process_ldap_queue_item, process_ldap_queue
- Refactor all LDAP signals (employee, department, group, position)
  to enqueue operations on DirectoryLdapError/DirectoryServiceError
- Add _queue.py helper for enqueuing with automatic Celery dispatch
- Add Celery Beat periodic task (every 60s) for queue processing
- Add management command: ldap_queue (status/retry/cleanup)
- Add migration 0043_ldap_sync_queue
- Add 18 tests covering model, tasks, signals, and enqueue logic
- Include LDAP services refactoring (base_service, constants,
  user_login_service, user_mapper_service, user_password_service,
  phone_utils)
2026-03-19 15:07:34 +03:00
lizerk
0539f50233 refactor: переход на строгий AD-only LDAP, регистрация + верификация email
- Удалён LDAP_SERVER_TYPE и все OpenLDAP ветки кода
- user_service.py: только AD (sAMAccountName, UAC, microsoft.modify_password)
- auth_backends.py: AD фильтры и атрибуты (objectCategory, objectGUID)
- Удалён server_configs.py (больше не нужен)
- Удалён OpenLDAP Docker окружение (docker-compose.ldap.yml, ldap/, QUICKSTART_LDAP.md)
- Фронтенд: мульти-шаговая регистрация, AvatarCropper 3:4, verify-email страница
- Убран CSRF для JWT API, IP-ограничения работают независимо
- Полевые ошибки регистрации с красной подсветкой на фронте
2026-03-19 00:48:13 +03:00
lizerk
df93afbcb1 fix(ldap): исправлен primary_key в ORM моделях
ПРОБЛЕМА:
- cn/ou были установлены как primary_key в LdapUser/LdapGroup/LdapOrganizationalUnit
- CN/OU не уникальны глобально (могут быть в разных OU)
- .objects.get(cn=...) возвращал случайную группу при дубликатах
- Нарушался дизайн django-ldapdb, где dn - базовый primary_key

РЕШЕНИЕ:
- Убран primary_key=True из cn/ou полей
- Используется dn (Distinguished Name) как primary_key из базового класса
- DN - уникальный идентификатор объекта в LDAP, требуется для всех операций
- Удалено избыточное поле distinguished_name

ИЗМЕНЕНИЯ:
- orm_models.py: cn/ou теперь обычные CharField, dn наследуется из ldapdb.models.Model
- group_utils_orm.py: .get(cn=...) → .filter(cn=...).first() с предупреждением
- group_service.py: find_dn() и find_dn_wrapped() используют .filter().first()

ОБОСНОВАНИЕ ОТКАЗА ОТ objectGUID:
- objectGUID неизменяем и уникален, НО:
- LDAP протокол требует DN для всех операций (add/modify/delete/rename)
- django-ldapdb жёстко завязан на self.dn для save()/delete()
- GUID можно использовать только в search filter, результат всё равно возвращает DN
- DN - естественный primary key для LDAP объектов
2026-03-18 19:14:34 +03:00
lizerk
1b03ed8a7b docs(ldap): улучшена документация orm_models.py
Добавлена подробная документация о:
- Ограничениях django-ldapdb (ModifyDN, динамический DN, collision handling)
- Архитектуре использования ORM vs ldap3 для разных операций
- Структуре DN для пользователей/групп/OU в Active Directory
- Роли base_dn в моделях (только для SUBTREE search, не для create/move)

Улучшены docstring для:
- LdapUser: объяснено где реально хранятся пользователи (по отделам, Dismissed)
- LdapGroup: объяснена структура групп (глобальные, роли отделов)
- LdapOrganizationalUnit: структура OU отделов
- get_users_base() и get_base_dn(): приоритеты и покрытие DN

Это решает вопрос о том, как модель работает с пользователями в разных OU
при переводах между отделами и увольнениях.
2026-03-18 19:02:11 +03:00
lizerk
1ffcee9d8a refactor(ldap): удалены неиспользуемые файлы и методы из employees/ldap
- Удалён orm_services.py (424 строки) — не использовался нигде
- Удалён group_utils.py (142 строки) — заменён group_utils_orm.py
- Перенесена _desired_group_cns_for_employee в group_utils_orm.py
- DirectoryService: удалены ~200 строк неиспользуемых методов
  (position_*, department member helpers, conn-based group ops,
   employee_ids_to_dns, dns_to_employee_ids, etc.)
- Очищены __init__.py и utils/__init__.py от устаревших импортов
2026-03-18 18:53:07 +03:00
lizerk
c38493a18b refactor(ldap): sync_service employeeNumber через ORM
- sync_service: заменены read_attrs + modify_user_attrs на LdapUser ORM
  для записи employeeNumber при импорте пользователей
- Убран неиспользуемый импорт modify_user_attrs
2026-03-18 15:10:32 +03:00
lizerk
ea0a7e1f29 refactor(ldap): углубить замену ldap3 → ORM
- user_service: _enable_user через LdapUser ORM (убран ldap_modify_or_ignore)
- user_service: _unique_logins через LdapUser.objects.filter (убран is_taken)
- user_service: удалены импорты MODIFY_REPLACE, is_taken, ldap_modify_or_ignore
- group_service: set_description, list_members, add/remove/replace_members,
  find_dn, groups_with_member — все через LdapGroup ORM
- group_service: удалены ldap_modify_or_ignore, MODIFY_*, BASE, esc_filter
- department_service: _set_ou_managed_by через LdapOrganizationalUnit ORM
  (заменены ~60 строк ADD/REPLACE/DELETE логики на 4 строки ORM)
- department_service: удалены MODIFY_ADD/DELETE/REPLACE
- position_service: delete_position_group и _reconcile_position_nesting
  через groups_with_member (ORM) вместо conn.search

Итого: -94 строки, убраны 3 импорта ldap_repository из сервисов.
2026-03-18 14:48:27 +03:00