60 lines
6.9 KiB
Markdown
60 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` в записях журнала для уже удалённых на момент миграции сущностей —
|
|||
|
|
сознательно не выполнялась (план, раздел «Вне объёма»).
|