Пентест (docs/reviews/2026-09-27-pentest.md) и план 031 (Swagger, TLS) — план, не реализован.
032 Роль superadmin (без организации) и привязка admin/viewer к одной организации:
users.organization_id + CHECK, audit_log.organization_id (миграции 0010-0012);
require_org/scope_org во всех чтениях и записях, журнал и «Обзор» в границах
организации; пользователи, организации, типы устройств, настройки журнала — только superadmin.
033 Исправление находок ревью 032 (docs/reviews/2026-09-27-changes-032-review.md,
docs/reviews/2026-09-27-codebase-review.md):
- FK audit_log.organization_id ON DELETE SET NULL (миграция 0013) — удаление организаций;
- проверка организации в предпросмотре подсети;
- инвариант «роль — организация» по итоговому состоянию (повышение снимает организацию,
понижение требует её), 422/404 вместо обезличенных 409;
- одинаковый 404 для чужих и несуществующих объектов (VRF, устройство, parent_id, оператор);
- отказы удаления в журнале организации, счётчики типов в пределах организации;
- UI: живое поле «Организация» в диалоге пользователя, бейдж superadmin; род в текстах 404.
README актуализирован под ролевую модель.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
6.9 KiB
Markdown
61 lines
6.9 KiB
Markdown
# Итог: ролевая модель с привязкой к организации и суперадминистратором (изменение 032)
|
||
|
||
План: `docs/changes/032-role-model-org-scope/PLAN.md`. Ревью реализации — `docs/reviews/2026-09-27-changes-032-review.md`
|
||
(находки № 1–8) и `docs/reviews/2026-09-27-codebase-review.md` (находки № 9–15); все они исправлены в изменении 033
|
||
(`docs/changes/033-review-fixes-032/SUMMARY.md`) — этот файл описывает исходную реализацию 032 в части модели данных
|
||
и объёма прав, без деталей самих находок ревью.
|
||
|
||
## Модель данных
|
||
- `app/models.py`: `Role.superadmin`; `User.organization_id` (nullable, `FK organizations.id`, индекс) с
|
||
`CheckConstraint("is_active = false OR (role = 'superadmin') = (organization_id IS NULL)")` (имя `ck_users_role_org_scope` в модели — с 033);
|
||
`AuditLog.organization_id` (nullable, индекс, `FK organizations.id`).
|
||
- Миграции: `0010_user_role_superadmin.py` (значение enum `superadmin` через `autocommit_block()`),
|
||
`0011_users_organization_scope.py` (колонка `users.organization_id`, backfill ролей, CHECK после backfill),
|
||
`0012_audit_log_organization.py` (колонка `audit_log.organization_id`, backfill по `entity_id` для `organization`/`vrf`/`device`/`isp`/`prefix`;
|
||
для удалённых сущностей и системных типов (`user`/`journal`/`session`/`device_type`) остаётся `NULL` — видно только `superadmin`).
|
||
- Backfill ролей (решение 4 плана): бывшие `admin` → `superadmin`, `organization_id=NULL`; бывшие `viewer` →
|
||
`organization_id=NULL`, `is_active=false` (заблокированы до явного назначения организации `superadmin`).
|
||
- Удаление организации (`delete_org`) получило блокиратор `"users"` — организация с привязанными пользователями не удаляется.
|
||
|
||
## Права доступа
|
||
Реализовано по сводной таблице плана: `superadmin` управляет пользователями, организациями (создание/удаление) и
|
||
типами устройств (запись); `admin`/`viewer` работают только в своей организации (VRF, префиксы, адреса, устройства,
|
||
операторы, карточка организации, журнал, «Обзор»); типы устройств читают все роли.
|
||
|
||
- `app/security.py`: `admin_user` теперь пропускает роль ≠ `viewer` (то есть `admin` и `superadmin`); новая зависимость
|
||
`superadmin_user` — строго `Role.superadmin`.
|
||
- `app/services.py`: хелперы `require_org(user, organization_id, what)` (404 при чужой организации, `superadmin` — без
|
||
ограничений) и `scope_org(stmt, column, user)` (фильтр списков); `audit()` получил параметр
|
||
`organization_id` для простановки на журнальных записях (`refuse_delete()` — с 033).
|
||
- `app/api/v1/users.py` — все маршруты на `superadmin_user`; инвариант «нельзя обезвредить последнего активного
|
||
администратора» переведён на `superadmin`.
|
||
- `app/api/v1/refs.py`, `app/api/v1/prefixes.py`, `app/api/v1/journal.py`, `app/api/v1/overview.py` — `require_org`/`scope_org`
|
||
во всех точках чтения и записи, разделение прав на организации/типы устройств/журнал по сводной таблице.
|
||
- `app/main.py`: `seed()` создаёт пользователя-бутстрап с `role=Role.superadmin`, `organization_id=None`.
|
||
|
||
## UI (`web/app.js`, `web/styles.css`)
|
||
- `ROLE_RU` дополнен «Суперадминистратор»; `isAdmin()` = роль ≠ `viewer`, новая `isSuperadmin()`.
|
||
- Пункт навигации «Пользователи» — только `isSuperadmin()`. `orgSwitcher()` интерактивен только для `superadmin`; для
|
||
`admin`/`viewer` — статичное название их организации.
|
||
- Экран «Организации»: «Добавить организацию» и «Удалить» — только `superadmin`.
|
||
- Экран «Пользователи»: диалог создания/правки получил выбор организации (скрывается для роли «Суперадминистратор»).
|
||
- Диалог типов устройств: управление — только `superadmin`, список виден всем.
|
||
|
||
## Отклонения от плана
|
||
Без отклонений в объёме прав и модели данных. Первый проход реализации содержал несколько дефектов (сломанное
|
||
удаление организации, невалидируемый инвариант «роль — организация» при отсутствии поля в теле запроса, нерабочая
|
||
смена роли на/с `superadmin`, невидимость `*.delete_blocked` в журнале организации, оракулы существования чужих
|
||
объектов, глобальные счётчики типов устройств и мелкие UI-огрехи) — все исправлены отдельным циклом, изменение 033.
|
||
|
||
## Проверено
|
||
- `import app.main`, `node --check web/app.js` — чисто.
|
||
- Миграции 0010–0012 применяются на стенде; `alembic check` без расхождений (после 033 — включая 0013).
|
||
- Изоляция чтения/записи по организациям, роли `admin`/`viewer`/`superadmin`, управление типами устройств и
|
||
пользователями — проверено на стенде (см. протокол сценариев в `docs/reviews/2026-09-27-changes-032-review.md`
|
||
и итоговые проверки в `docs/changes/033-review-fixes-032/SUMMARY.md`).
|
||
|
||
## Не проверено
|
||
- Работоспособность 032 отдельно от 033 — не актуально (033 доводит 032 до готовности, коммитятся вместе).
|
||
- Полная историческая реконструкция `organization_id` в записях журнала для уже удалённых на момент миграции сущностей —
|
||
сознательно не выполнялась (план, раздел «Вне объёма»).
|