12 KiB
Роли доступа и матрица прав
Ordinis использует Keycloak realm roles (claim realm_access.roles в JWT) для двух уровней контроля доступа:
- Scope ACL — что пользователь может читать (видимость записей и словарей)
- 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 в Keycloakordinisclient). Default fallback —realm_access.roles. Настраивается черезORDINIS_AUTH_ROLES_CLAIMenv 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:restrictedordinis:schema:reviewerordinis:record:reviewer
Может всё что maker, плюс approve / reject чужих drafts. Publish — отдельной ролью.
Publisher
ordinis:client:restrictedordinis: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:restrictedordinis:schema:reviewerordinis:schema:publisherordinis:record:reviewer
Composite-роль в Keycloak автоматически expands в JWT — token содержит все включённые роли. Это работает и без super-role override.
Настройка в Keycloak
Создание ролей
- Открой Keycloak Admin Console → realm
nstart→ Realm roles. - Нажми Create role и создай по очереди:
ordinis:client:publicordinis:client:internalordinis:client:restrictedordinis:schema:reviewerordinis:schema:publisherordinis:record:reviewer
Composite role ordinis:admin (опционально, но удобно)
- Create role →
ordinis:admin. - На вкладке роли → Action → Add associated roles → выбери все 6 ролей выше → Assign.
После этого можно назначать одну роль ordinis:admin users'ам, и они автоматически получают все права.
Заметка про token mapper: composite role в Keycloak разворачивается в JWT только если есть соответствующий mapper. У client
ordinisв realmnstartдолжен быть 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 это двумя путями:
- Mapper настроен (рекомендуется): JWT содержит all 6 expanded roles →
WorkflowRoles.hasRole(specific)находит её напрямую.- 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) перед состоянием перехода.
Назначение пользователю
- Users → найди user'а → Role mapping.
- Assign role → отметь нужные роли (или
ordinis:adminдля админов).
Применение
Пользователю нужно logout / login в Ordinis UI, чтобы получить новый JWT с обновлённым realm_access.roles claim'ом.
Диагностика
«Не могу нажать Approve — кнопка скрыта»
UI скрывает кнопку если у user'а нет нужной workflow роли. Проверить:
- F12 → Application → Session Storage →
oidc.user:.... - В JSON найти
profile.realm_access.roles. - Должна быть
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.
- Phase 2 (2026-05-13, vечером) — per-dict
approvalMinRoleenforced. Поле существовало с миграции 0019, но backend его игнорировал (UI hint прямо говорил "(Phase 2 — enforcement TBD)"). Теперь required gate в approve/reject/decide/publish.