Files
ipam_control/docs/changes/032-role-model-org-scope/SUMMARY.md
T
ayurishchevandClaude Opus 5.5 744a025960 Задачи 032-033: ролевая модель с привязкой к организации, исправления по ревью
Пентест (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>
2026-09-27 11:26:57 +03:00

6.9 KiB
Raw Blame History

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