diff --git a/AUDIT.md b/AUDIT.md new file mode 100644 index 0000000..d1edbf3 --- /dev/null +++ b/AUDIT.md @@ -0,0 +1,168 @@ +# Аудит проекта ACORE + +**Система управления аптечной сетью** · корпоративный портал + внутренние интеграции +Дата аудита: **2026-07-22** · Ветка: `main` · Django 6.0.3 / Python 3.14 / PostgreSQL + +--- + +## 1. Резюме + +ACORE — внутренний портал аптечной сети: управление сотрудниками, аптеками, справочниками, ИТ-инвентарём, задачами, лентой публикаций, плюс живые интеграции с Active Directory и Mattermost. Проект в **хорошей инженерной форме**: архитектура последовательная, паттерны выдержаны, безопасность продумана на уровне выше среднего для внутренних систем. Растёт быстро — 13 приложений, ~14,4 тыс. строк Python (без миграций и тестов). + +**Общая оценка: 7.5/10.** Фундамент добротный. Главные риски не архитектурные, а процессные: неравномерное покрытие тестами, большой объём незакоммиченной работы и хранение секретов интеграций открытым текстом. + +| Показатель | Значение | +|---|---| +| Приложений | 13 | +| Моделей | 39 | +| Python-код (без миграций/тестов) | ~14 400 строк | +| Миграций | 56 | +| Тестов | 127 функций (~2000 строк) | +| Шаблонов | 39 HTML | +| JS-модулей (кастомных) | 12 | +| `manage.py check` | чисто | +| `check --deploy` (production) | 2 минорных предупреждения | +| Миграции консистентны | да (`No changes detected`) | + +--- + +## 2. Архитектура + +### Технологический стек +- **Backend:** Django 6.0.3, PostgreSQL (psycopg2), Django REST Framework (точечно, 5 endpoint-ов). +- **Async:** Celery 5.5 + Redis + django-celery-beat/results. +- **Frontend:** серверный рендеринг + **HTMX** (навигация без перезагрузки), **Tailwind CSS v4** (сборка через CLI), ванильный JS для виджетов. SPA-фреймворков нет — сознательно. +- **Админка:** django-unfold (кастомная тема) + django-import-export. +- **Интеграции:** ldap3 (Active Directory), mattermostautodriver + httpx (Mattermost). +- **Контент:** django-tinymce (WYSIWYG) + nh3 (санитизация HTML против XSS). +- **Справочники:** django-fast-treenode (иерархические деревья). + +### Ключевые архитектурные решения +1. **Единый справочный слой** (`core.Directory` / `DirectoryType`) — иерархические справочники (организации, отделы, должности, регионы, типы/бренды оборудования, статусы). На него опирается почти всё. Реализовано аккуратно, с автоперестройкой дерева после миграций и импорта. +2. **Интеграции по единому паттерну:** сервис-клиент → sync-сервис → сигналы → Celery-задачи → management-команды. Благодаря этому AD лёг рядом с Mattermost без изобретения велосипеда. +3. **Единый шелл фронта** (`templates/index.html`) + страницы-фрагменты HTMX. Общие утилиты вынесены (`core/utils/htmx.py`, `permissions.py`, `templatetags/common_tags.py`). +4. **Роли через `auth.Group`**, а не поле-enum (миграция `27a917e`). + +--- + +## 3. Приложения и модели + +| Приложение | Назначение | Модели | Тесты | +|---|---|---|---| +| `core` | Справочники, конфиги коннекторов, портал-вью | Directory, DirectoryType, ConnectorConfig | 21 (смоук страниц, PII-доступ, дерево) | +| `employees` | Сотрудники (кастомный User), права | Employee, EmployeePermissionOverride, PermissionGroup | **0** | +| `locations` | Аптеки, склады, офисы (MTI) | Location, Pharmacy, PharmacyPhone, PharmacyHistory | **0** | +| `it_manager` | ИТ-инвентарь, заявки, интернет-договоры | Equipment, **EquipmentModel** (каталог), EquipmentAssignment, Consumable, Ticket, TicketComment, WorkLog, ServiceCall, InternetProvider, InternetContract | 10 | +| `feed` | Лента и публикации (TinyMCE) | Post, PostComment | 40 | +| `tasks` | Задачи (роли Битрикс-стиля) | Task, TaskComment | **0** ⚠ | +| `ad_connector` | Active Directory (провижининг) | ADSyncLog | 30 | +| `mattermost_connector` | Mattermost (аккаунты, каналы) | MattermostUser/Team/Channel + membership + sync-settings (7) | 19 | +| `visit_manager` | Учёт посещений, графики, смены | AttendanceLog, WorkShift, Shift, Holiday, Schedule* (9) | **0** | +| `hiring` | Заявки на найм | HiringRequest | 7 | +| `utilities` | Показания счётчиков | UtilityReading | 0 | +| `mail_connector`, `mailparser`, `portal_connector` | Заглушки / служебное | — | 0 | + +### Заметные модели +- **`Employee`** — кастомный `AbstractBaseUser`, богатый: контакты, PII, локация (аптека/офис/склад), руководитель, интеграционные поля AD/Mattermost, гибкие права через `EmployeePermissionOverride` (grant/revoke с датами действия). Валидация в `clean()` разделяет аптечных/офисных сотрудников. +- **`EquipmentModel` + `Equipment`** (свежий рефакторинг) — каталог моделей отделён от экземпляров: тип+бренд+модель заводятся один раз, экземпляры несут только серийник/локацию. Массовый ввод и массовая выдача. +- **`Task`** — три роли (ответственный/соисполнители/наблюдатели), видимость через `for_user()`, наблюдатели не меняют статус. + +--- + +## 4. Сильные стороны + +✅ **Безопасность продумана.** Отдельный коммит hardening (`faa06b9`): исправлен open redirect в логине, добавлена аутентификация на API, MIME-валидация загрузок (magic bytes + Pillow verify), HttpOnly/SameSite куки, лимиты загрузки. Есть полноценный `production.py` (DEBUG=False, SSL-redirect, HSTS, secure cookies) — `check --deploy` даёт лишь 2 минорных предупреждения. + +✅ **Разделение PII.** Личные данные (телефоны, ИНН, дата рождения, экстренный контакт) отделены от публичных полей — и в DRF-сериализаторах (`EmployeeSerializer` / `EmployeeDetailSerializer`), и на детальной странице сотрудника (`_can_view_employee_pii`: staff / сам / непосредственный руководитель). Покрыто тестами. + +✅ **XSS-защита ленты.** Тело поста (HTML из TinyMCE) всегда прогоняется через `nh3` в `Post.save()` — чистка в модели, а не во view, поэтому защищён любой путь записи. Загрузка картинок отдельным endpoint-ом с проверкой содержимого; `data:`-URI вырезаются. + +✅ **Оптимизация запросов.** Осознанная борьба с N+1: `Prefetch` с `select_related`, агрегаты вместо циклов `.count()`, `distinct=True` в `Count` при OR-джойнах по M2M. + +✅ **Устойчивость к сбоям.** Сигналы обёрнуты в try/except — недоступный Celery-брокер или AD не роняют сохранение сотрудника. Reverse-миграции пишутся (где возможно). + +✅ **Единый UX-язык.** HTMX + Tailwind + переиспользуемые виджеты (people-picker дерево, searchable-select, пагинация). Украинский интерфейс выдержан. + +--- + +## 5. Тестирование + +**127 тестовых функций, ~2000 строк.** Покрытие качественное там, где оно есть, но **неравномерное**: + +**Покрыто хорошо:** +- `feed` (40) — публикация, права, санитизация, картинки, комментарии. +- `ad_connector` (30) — LDAP-клиент на мок-каталоге ldap3 (`MOCK_SYNC`), sync-сценарии, сигналы, страница сопоставления. +- `core` (21) — смоук всех страниц портала, PII-доступ, дерево справочников. +- `mattermost_connector` (19), `it_manager` (10), `hiring` (7). + +**Не покрыто вообще (0 тестов):** +- ⚠ **`tasks`** — модуль с нетривиальной логикой видимости и ролей, но без тестов (задача осталась в бэклоге). +- ⚠ **`employees`** — кастомная модель User с валидацией `clean()`, правами-оверрайдами; критичный код без тестов. +- ⚠ **`visit_manager`** — 9 моделей (графики, смены, посещения) без единого теста. +- `locations`, `utilities`, mail/portal-заглушки. + +**Инфраструктурный нюанс:** discovery `manage.py test apps.X` падает на структуре пакетов — тесты запускают явными метками модулей (`apps.feed.tests`). CI отсутствует — тесты гоняются вручную. + +--- + +## 6. Безопасность — находки + +| Приоритет | Находка | Детали | +|---|---|---| +| 🔴 Высокий | **Секреты интеграций открытым текстом** | `ConnectorConfig.password` / `token` — обычные `CharField` в БД. Пароль сервисной учётки AD и токен Mattermost лежат в базе как есть. Рекомендация: шифрование на уровне поля (`django-fernet-fields` или аналог) либо вынос в секрет-менеджер/переменные окружения. | +| 🟡 Средний | **Нет тестов на критичный код доступа** | `tasks.for_user()`, `employees` права, PII-гейтинг частично — регрессии в контроле доступа не отлавливаются автоматически. | +| 🟢 Низкий | `SECURE_HSTS_PRELOAD` не выставлен | `check --deploy` W021 — не даёт подать сайт в preload-список. Косметика. | +| 🟢 Низкий | Синхронные операции AD с паролем блокируют запрос | Сохранение формы ждёт LDAP-вызов (до 15 с таймаут при недоступном контроллере). Ошибка не роняет сохранение, но UX-задержка возможна. | + +**Хорошо:** `.env` и `db.sqlite3` **не** в git; `.gitignore` покрывает env/sqlite/pycache/node_modules/шаблон-референс; SECRET_KEY/ALLOWED_HOSTS/DATABASE_URL — из окружения. + +--- + +## 7. Конфигурация и деплой + +- **Настройки разделены** на `base` / `local` / `production`. Production корректен. +- **БД:** PostgreSQL и в dev, и в prod (через `DATABASE_URL`). Лежащий в корне `db.sqlite3` — мёртвый артефакт, стоит удалить. +- **Celery:** приложение сконфигурировано (`acore/celery.py`), брокер из env, локально `CELERY_TASK_ALWAYS_EAGER=True` (без брокера задачи синхронно). Beat-задачи есть в mattermost/ad коннекторах. +- **Локализация:** `LANGUAGE_CODE='ru'`, `TIME_ZONE='Europe/Kiev'`, UI на украинском, `verbose_name` на русском, комментарии — смесь ru/uk. Работает, но единого языка нет. +- **Нет:** README, `docs/`, CI/CD (`.github`/`.gitlab-ci`), Makefile, зафиксированного процесса деплоя. + +--- + +## 8. Технический долг — сводка по приоритетам + +### 🔴 Высокий +1. **Незакоммиченная работа.** В рабочем дереве **116 изменённых/новых файлов** — целые модули (feed, tasks, employee-detail, equipment-каталог, виджеты) не в истории git. Последний коммит — `c3f56f8`. Риск потери работы; история не отражает реальное состояние. **Закоммитить логическими блоками.** +2. **Секреты открытым текстом** (см. §6). + +### 🟡 Средний +3. **Тесты для `tasks`, `employees`, `visit_manager`** — критичная бизнес-логика без покрытия. +4. **Нет CI** — тесты не гоняются автоматически на push; регрессии всплывают вручную (в этой сессии дважды находились сломанные смоук-URL). +5. **Рост числа сигналов на `Employee.post_save`** (9 ресиверов между AD и Mattermost, у каждого свой pre_save-кеш). При третьей интеграции (Carbonio в планах) станет хрупким — напрашивается единый диспетчер изменений. +6. **Reverse-миграции частично ломаются** на восстановлении non-null полей (напр. `0018_equipment_model_catalog`) — forward безупречен, но откат в dev упадёт. Приемлемо, но стоит знать. + +### 🟢 Низкий +7. **Дубль зависимости:** `import-export==0.3.1` (устаревший неофициальный) соседствует с `django-import-export==4.4.0`. Проверить и убрать лишнее. +8. **Единый язык** кода/комментариев/verbose_name. +9. **Мёртвые артефакты:** `db.sqlite3` в корне, `.env2`. +10. **Портал наполовину макет:** email, chat, документы, календарь, часть HR — статичные страницы без бэкенда. Стоит явно решить, что реализуем, а что убрать из меню. + +--- + +## 9. Рекомендации (порядок действий) + +1. **Закоммитить накопленное** логическими блоками — снять риск №1 немедленно. +2. **Зашифровать секреты `ConnectorConfig`** перед подключением живого AD в прод — именно эта логика трогает внешнюю систему, где ошибка видна всей компании. +3. **Покрыть тестами `tasks` и `employees`** — минимум контроль доступа и валидацию. Мок-сценарии для AD/Mattermost уже почти готовые тесты. +4. **Поднять минимальный CI** (GitHub Actions / GitLab CI): `manage.py check` + прогон тестов + `makemigrations --check`. Дёшево, ловит регрессии автоматически. +5. **Написать README** с процессом запуска (dev-сервер, Tailwind-сборка, Celery, тесты) и деплоя. +6. **Отрефакторить сигналы `Employee`** в единый диспетчер до третьей интеграции. +7. **Причесать:** удалить `db.sqlite3`/`.env2`, разобраться с дублем `import-export`, определиться с макет-страницами. + +--- + +## 10. Вывод + +Фундамент выбран правильно и выдержан — проект масштабируется **добавлением модулей, а не переписыванием**. Паттерны интеграций, справочный слой, безопасность, борьба с N+1 — всё на уровне выше среднего для внутренней системы. Основной долг — **процессный**, а не архитектурный: закоммитить работу, зашифровать секреты, дописать тесты на критичные модули и поднять CI. Закрыв эти пункты, проект будет готов к спокойной эксплуатации и дальнейшему росту. + +--- +*Аудит подготовлен на основе статического анализа кода, конфигурации, миграций, тестов и git-истории. Данные собраны инструментально (`manage.py check --deploy`, `makemigrations --check`, подсчёт моделей/тестов/строк).*