docs(rbac): добавить матрицу realm-ролей Keycloak

operator/rbac.md — новая страница с полной матрицей RBAC:
- Scope ACL (ordinis:client:public/internal/restricted)
- Workflow роли (schema:reviewer, schema:publisher, record:reviewer)
- Action matrix (endpoint → required scope + role)
- Типичные профили (consumer, maker, reviewer, publisher, admin)
- Composite role 'ordinis:admin' для one-shot assignment
- Keycloak setup инструкция (Admin Console шаги)
- Диагностика типичных 403 ошибок

toc.yaml — link в раздел Руководство оператора.
operator/index.md, reviewer/index.md — cross-references к rbac.md.

Покрывает Phase 1d enforcement (MR !162) — пользователи теперь
знают какие роли назначать в Keycloak realm 'nstart'.
This commit is contained in:
Zimin A.N.
2026-05-13 12:47:50 +03:00
parent c5b00ff623
commit 1dba9476db
4 changed files with 155 additions and 0 deletions
+2
View File
@@ -19,6 +19,8 @@
- [Закрытие записи](close-record.md) — как корректно «удалить» запись,
сохранив историю (bitemporal модель)
- [Журнал аудита](audit-log.md) — как посмотреть, кто и когда что менял
- [Роли доступа (RBAC)](rbac.md) — полная матрица realm-ролей Keycloak:
scope ACL + workflow roles (schema reviewer, publisher, record reviewer)
- [Разбор ошибок](errors.md) — типичные ошибки валидации и как их понять
## Базовые понятия
+144
View File
@@ -0,0 +1,144 @@
# Роли доступа и матрица прав
Ordinis использует **Keycloak realm roles** (claim `realm_access.roles` в JWT) для двух уровней контроля доступа:
1. **Scope ACL** — что пользователь может **читать** (видимость записей и словарей)
2. **Workflow roles** — какие **действия** пользователь может выполнять (approve, publish, reject)
Все роли используют colon-separated namespace `ordinis:<домен>:<действие>`.
## Полная матрица ролей
### Scope ACL (видимость данных)
| Realm role | Видит | Подразумевает |
|---|---|---|
| `ordinis:client:public` | только PUBLIC записи | — |
| `ordinis:client:internal` | PUBLIC + INTERNAL | автоматически включает public |
| `ordinis:client:restricted` | PUBLIC + INTERNAL + RESTRICTED | автоматически включает оба нижних |
Без любой из этих ролей пользователь считается **anonymous** и видит только `PUBLIC` записи (fallback).
> Альтернативные dash-формы (`ordinis-public`, `ordinis-internal`, `ordinis-restricted`) также распознаются для совместимости. Канонические — colon-форма.
### Workflow роли (право на действия)
| Realm role | Право |
|---|---|
| `ordinis:schema:reviewer` | Approve / Запросить правки / Отклонить **schema draft** |
| `ordinis:schema:publisher` | Publish approved schema draft → live |
| `ordinis:record:reviewer` | Approve / Reject **record draft** (Approval Workflow v2) |
Backend проверяет роль перед каждым действием. Без роли — `403 forbidden_role` с сообщением о требуемой роли. Frontend дополнительно скрывает кнопки, для которых нет роли.
> **Maker side** (создание / submit / withdraw draft'а) специальной ролью не gated — любой authenticated user с required scope может создать draft. Backend защищает invariant maker-checker через `403 self_approve_forbidden` (один и тот же user не может approve свой draft).
## Полная матрица действий
| Действие | Endpoint | Scope требуется | Workflow роль требуется |
|---|---|---|---|
| Просмотр PUBLIC dict | `GET /api/v1/dictionaries` | public+ | — |
| Просмотр INTERNAL/RESTRICTED dict | `GET /api/v1/dictionaries` | соответствующий scope | — |
| Создание dict | `POST /api/v1/dictionaries` | соответствующий scope | — |
| Редактирование dict schema (через draft) | `POST /api/v1/dictionaries/{name}/drafts` | соответствующий scope | — (maker) |
| Submit schema draft на ревью | `POST .../drafts/{id}/review` | соответствующий scope | — (maker) |
| **Approve / Request changes / Reject schema draft** | `POST .../drafts/{id}/decision` | соответствующий scope | `ordinis:schema:reviewer` |
| **Publish approved schema draft** | `POST .../drafts/{id}/publish` | соответствующий scope | `ordinis:schema:publisher` |
| Withdraw собственный schema draft | `POST .../drafts/{id}/withdraw` | соответствующий scope | — (maker self) |
| Создание record draft | `POST .../records/.../drafts` | соответствующий scope | — (maker) |
| **Approve record draft** | `POST /api/v1/drafts/{id}/approve` | соответствующий scope | `ordinis:record:reviewer` |
| **Reject record draft** | `POST /api/v1/drafts/{id}/reject` | соответствующий scope | `ordinis:record:reviewer` |
| Просмотр webhook subscriptions | `GET /api/v1/admin/webhooks/subscriptions` | `internal` или `restricted` | — |
| Просмотр audit log | `GET /api/v1/admin/audit` | соответствующий scope | — |
## Типичные профили пользователей
### Только чтение (consumer, integrator)
- `ordinis:client:public`
Видит публичные справочники, не может ничего менять.
### Maker (контент-редактор)
- `ordinis:client:internal` (видит данные своей области)
Создаёт и редактирует справочники, записи, schema drafts, submit'ит их на ревью. **Не может** approve / publish свои собственные действия (maker-checker).
### Reviewer
- `ordinis:client:restricted`
- `ordinis:schema:reviewer`
- `ordinis:record:reviewer`
Может всё что maker, плюс approve / reject чужих drafts. Publish — отдельной ролью.
### Publisher
- `ordinis:client:restricted`
- `ordinis:schema:reviewer` (опционально)
- `ordinis:schema:publisher`
Финальный gatekeeper для production публикации схем. Обычно более узкая группа чем reviewers.
### Admin (полный доступ)
- `ordinis:client:restricted`
- `ordinis:schema:reviewer`
- `ordinis:schema:publisher`
- `ordinis:record:reviewer`
Или **composite role** `ordinis:admin`, которая агрегирует все четыре.
## Настройка в Keycloak
### Создание ролей
1. Открой **Keycloak Admin Console** → realm `nstart`**Realm roles**.
2. Нажми **Create role** и создай по очереди:
- `ordinis:client:public`
- `ordinis:client:internal`
- `ordinis:client:restricted`
- `ordinis:schema:reviewer`
- `ordinis:schema:publisher`
- `ordinis:record:reviewer`
### Composite role `ordinis:admin` (опционально, но удобно)
3. **Create role**`ordinis:admin`.
4. На вкладке роли → **Action****Add associated roles** → выбери все 6 ролей выше → **Assign**.
После этого можно назначать одну роль `ordinis:admin` users'ам, и они автоматически получают все права.
### Назначение пользователю
1. **Users** → найди user'а**Role mapping**.
2. **Assign role** → отметь нужные роли (или `ordinis:admin` для админов).
### Применение
Пользователю нужно **logout / login** в Ordinis UI, чтобы получить новый JWT с обновлённым `realm_access.roles` claim'ом.
## Диагностика
### «Не могу нажать Approve — кнопка скрыта»
UI скрывает кнопку если у user'а нет нужной workflow роли. Проверить:
1. F12 → Application → Session Storage → `oidc.user:...`.
2. В JSON найти `profile.realm_access.roles`.
3. Должна быть `ordinis:schema:reviewer` (для schema) или `ordinis:record:reviewer` (для record).
Если роли нет — назначить в Keycloak и сделать logout/login.
### «403 forbidden_role» от backend
Backend вернул 403 с кодом `forbidden_role`. Сообщение содержит требуемую роль. Назначь её в Keycloak и refresh JWT.
### «403 self_approve_forbidden»
Пользователь пытается approve свой собственный draft. Это **invariant maker-checker** — backend защищает от self-approve. Нужен другой reviewer.
### «404 / пустая страница на /webhooks»
Webhook subscriptions требуют `internal` или `restricted` scope. Назначь `ordinis:client:internal` минимум.
## История
- **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.