docs(rbac): обновить RBAC sections после Phase 1d + Phase 2

This commit is contained in:
Александр Зимин
2026-05-13 16:41:03 +00:00
parent 0e165f69ca
commit c41e34545b
2 changed files with 49 additions and 8 deletions
+32 -8
View File
@@ -407,14 +407,38 @@ curl -X POST /api/v1/dictionaries/mission/drafts/8f3a.../publish -d '{
| 403 | `maker_only` | submit/withdraw от чужого actor'а |
| 422 | `breaking_change_requires_draft` | inline PATCH вернул это — переходи в workflow |
## RBAC (TBD)
## RBAC
В текущей реализации workflow доступен любому authenticated user'у через
network-level auth policy. Когда Keycloak roles claim прорастёт в JWT,
будут отдельные требования:
Phase 1d (2026-05-13) — backend enforced. Реализация в [`WorkflowRoles`](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/-/blob/main/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/auth/WorkflowRoles.java). Полная матрица — [`docs/operator/rbac.md`](../../operator/rbac.md).
- `dict_schema_maker` — для create / submit / withdraw
- `dict_schema_reviewer` — для decision
- `dict_schema_publisher` — для publish
| Действие | Workflow role |
|---|---|
| create / submit / update / withdraw schema draft | — (maker — любой authenticated с scope ACL) |
| decide (approve / request_changes / reject) | `ordinis:schema:reviewer` |
| publish (approved → live) | `ordinis:schema:publisher` |
| Super-role для всех workflow actions | `ordinis:admin` |
См. record-level `DraftController` для reference architecture pool model.
Naming convention — colon-separated `ordinis:<domain>:<action>`, consistent
со scope ACL (`ordinis:client:public/internal/restricted`).
**Phase 2 (2026-05-13) — per-dict минимальная роль ревьюера.** Поле
`approvalMinRole` на `DictionaryDefinition` (set'ится через
`DictionaryEditorDialog` или import CUOD-bundle манифеста). Если задано —
backend требует чтобы reviewer имел эту роль **дополнительно** к
стандартной `schema:reviewer` / `schema:publisher`. `ordinis:admin`
проходит автоматически. Use case: критичный справочник
(`approvalMinRole = "ordinis:restricted"`) — approve может только user с
restricted-scope role'ю.
**JWT claim path:** на staging/prod — `resource_access.ordinis.roles`
(client-scoped Keycloak roles), default fallback `realm_access.roles`.
Configurable через `ORDINIS_AUTH_ROLES_CLAIM`. `usePermissions.ts` на
frontend читает оба пути.
**Maker-checker invariant:** maker (creator) не может decide свой
собственный draft — 409 `self_approve_forbidden`. Backend rule, не
обходится через admin role.
См. также:
- [`docs/operator/rbac.md`](../../operator/rbac.md) — полная матрица ролей + setup в Keycloak (включая composite `ordinis:admin` role и mapper).
- `DraftController` (record drafts) — reference architecture pool model с теми же gates.
+17
View File
@@ -112,6 +112,22 @@ Composite-роль в Keycloak автоматически expands в JWT — tok
После этого можно назначать одну роль `ordinis:admin` users'ам, и они автоматически получают все права.
> **Заметка про token mapper:** composite role в Keycloak **разворачивается в JWT** только если есть соответствующий mapper. У client `ordinis` в realm `nstart` должен быть mapper типа **User Realm Role** или **User Client Role** с `Multivalued: ON` и `Add to access token: ON`. Без mapper'а composite роль будет на user'е, но в JWT попадёт только сама `ordinis:admin`, а её components (`ordinis:schema:reviewer` и т.д.) — нет.
>
> Backend handles это двумя путями:
> 1. **Mapper настроен** (рекомендуется): JWT содержит all 6 expanded roles → `WorkflowRoles.hasRole(specific)` находит её напрямую.
> 2. **Mapper не настроен** (default Keycloak install): JWT содержит только `ordinis:admin` → `WorkflowRoles.hasRole(role)` всё равно проходит через super-role override (`roles.contains(ADMIN)`).
>
> Super-role override — fallback. Mapper — proper solution. Если admin user'ов больше 5 — настройте mapper, чтобы scope ACL и audit log видели правильные конкретные роли.
### Per-dictionary минимальная роль ревьюера (Phase 2)
В `DictionaryEditorDialog` есть поле **«Минимальная роль ревьюера (опционально)»** (`approvalMinRole`). Если задано (e.g. `ordinis:restricted`) — backend требует чтобы reviewer держал эту роль **поверх** стандартной `ordinis:record:reviewer` / `ordinis:schema:reviewer` / `ordinis:schema:publisher`.
Use case: критичный справочник (PII, госуд. реестр) — `approvalMinRole = ordinis:restricted` гарантирует что approve может только user с restricted-scope ролью, даже если у него есть reviewer на других dict'ах. `ordinis:admin` super-role проходит всегда.
Поле сохраняется в DB как plain string. Backend (`DraftService.requireDictMinRoleIfSet`, `SchemaDraftService.requireDictMinRoleIfSet`) делает `workflowRoles.requireRole(approvalMinRole)` перед состоянием перехода.
### Назначение пользователю
1. **Users** → найди user'а**Role mapping**.
@@ -149,3 +165,4 @@ Webhook subscriptions требуют `internal` или `restricted` scope. На
- **Phase 1a/1b/1c** (до 2026-05-12) — backend гейтил только maker-checker, любой authenticated мог review.
- **Phase 1d** (2026-05-13) — backend проверяет workflow роли; frontend скрывает кнопки. Migration require: создать роли в Keycloak и assign actual reviewer-users до или одновременно с deploy.
- **Phase 2** (2026-05-13, vечером) — per-dict `approvalMinRole` enforced. Поле существовало с миграции 0019, но backend его игнорировал (UI hint прямо говорил "(Phase 2 — enforcement TBD)"). Теперь required gate в approve/reject/decide/publish.