Files
mdm-ordinis/docs/operator/rbac.md
T
Zimin A.N. 36c06ae1da feat(auth): WorkflowRoles configurable claim path + ordinis:admin super-role
User report: token has roles в resource_access.ordinis.roles (client-scoped),
backend WorkflowRoles читал hardcoded realm_access.roles → forbidden_role
для всех reviewer actions.

Backend:
- WorkflowRoles: depends OrdinisAuthProperties → reads via rolesClaim path
  (же что ScopeContext). Default realm_access.roles, на staging/prod env
  var ORDINIS_AUTH_ROLES_CLAIM=resource_access.ordinis.roles (уже wired
  в helm helper).
- Nested claim path resolver — mirror ScopeContext.readClaimByPath
- New super-role ADMIN ('ordinis:admin') — holder автоматически проходит
  любой workflow check без явного назначения отдельных ролей. Convenience
  для small teams.

Frontend (usePermissions.ts):
- tokenRoles() читает оба claim path (realm_access.roles +
  resource_access.ordinis.roles) и объединяет — UI gate работает
  независимо от backend config
- hasRole() recognizes 'ordinis:admin' как super-role override
- ROLE_ADMIN exported

Docs:
- docs/operator/rbac.md: добавлен ordinis:admin row, JWT claim path note,
  Admin profile updated (super-role vs composite explanation)
2026-05-13 13:17:54 +03:00

152 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Роли доступа и матрица прав
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) |
| `ordinis:admin` | **Super-role** — автоматически разрешает любой workflow action. Convenience для small teams: assign one role вместо трёх. |
Backend проверяет роль перед каждым действием. Без роли — `403 forbidden_role` с сообщением о требуемой роли. Frontend дополнительно скрывает кнопки, для которых нет роли.
> **JWT claim path:** на staging/prod backend читает роли из `resource_access.ordinis.roles` (client-scoped roles в Keycloak `ordinis` client). Default fallback — `realm_access.roles`. Настраивается через `ORDINIS_AUTH_ROLES_CLAIM` env var.
> **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 (полный доступ)
**Простой путь** — single role `ordinis:admin` (super-role): backend и frontend оба recognize её как override для любого workflow check'а. Достаточно одного назначения.
**Канонический путь (composite в Keycloak)**`ordinis:admin` как composite, агрегирующий:
- `ordinis:client:restricted`
- `ordinis:schema:reviewer`
- `ordinis:schema:publisher`
- `ordinis:record:reviewer`
Composite-роль в Keycloak автоматически expands в JWT — token содержит все включённые роли. Это работает и без super-role override.
## Настройка в 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.