Перейти к основному содержимому

Модель прав доступа

Зачем нужно

Документ задаёт единую модель permissions для всех 7 доменов. Identity владеет таблицами ролей и назначений, но формат permission и правила проверки общие.

Формат permission

<domain>.<resource>.<action>[.<scope>]
ЧастьОписаниеПример
domainодин из 7 доменов или platformlms
resourceсущность или агрегатcourse
actionдействиеread, manage, publish, consume
scopeопционально, ограничениеown, family, organization, team

Resource segment uses kebab-case plural nouns for product-facing aggregates unless a domain already has a stable canonical plural (oauth_clients remains underscore only if it is already in the catalog). New permissions must match:

^(identity|storefront|crm|lms|task-bank|competitions|management|platform)\.[a-z0-9-]+\.[a-z0-9-]+(\.[a-z0-9-]+)?$

Do not introduce camelCase permission keys. If a domain needs to keep an existing snake_case resource key for backward compatibility, it must be explicitly listed in packages/permissions/catalog.ts as a legacy key.

Примеры:

  • identity.users.read
  • identity.users.manage
  • lms.courses.read
  • lms.courses.manage.own
  • lms.attendance.write
  • lms.learning-groups.read.assigned
  • lms.learning-groups.manage.assigned
  • lms.attempts.manage.assigned
  • crm.invoices.manage
  • crm.refunds.manage
  • task-bank.problems.publish
  • competitions.results.publish
  • management.dashboards.read
  • management.team-tasks.assign
  • platform.feature-flags.manage
  • platform.reference-data.manage

Actions (минимальный набор)

ActionСемантика
readчтение объектов
listчтение коллекций (если разделение нужно)
createсоздание
updateизменение
deleteудаление
manageагрегат: read + create + update + delete
publishвывод в публичный/официальный статус
archiveархивация
exportвыгрузка в файл
assignназначение роли/задачи
consumeпотребление (entitlement, slot)
approveсогласование

Scopes

ScopeКогда применяется
ownобъект принадлежит пользователю
familyобъект принадлежит семье пользователя
organizationобъект принадлежит организации, в которой пользователь — член
teamобъект принадлежит команде организации
assignedпользователь явно назначен на объект (преподаватель к learning group/session/review)
globalбез ограничений

Если scope не указан, действует global.

Service scopes

Service-to-service permissions use the service:<permission> form. The part after service: must match a catalog permission from packages/permissions/catalog.ts; custom free-form service scopes are forbidden.

Examples:

Service scopeBase permissionIntended caller
service:crm.entitlements.readcrm.entitlements.readlms-api, competitions-api
service:crm.entitlements.consumecrm.entitlements.consumelms-api
service:competitions.results.readcompetitions.results.readmanagement-api
service:platform.reference-data.readplatform.reference-data.readall backend services

Service tokens are issued only by identity OAuth client_credentials flow, have audience-limited JWT access tokens, and are logged in audit as actor_context.kind = oauth_client.

Роли

Роли — это именованные наборы permissions. Identity владеет:

  • глобальными ролями (admin, support, manager, analyst);
  • семейной adult membership (family_adult); ребёнок представлен через student_profile, а не через отдельную family role;
  • организационными ролями и memberships (owner, admin, member) внутри конкретной организации.

Роли не хранятся в каждом домене отдельно. Каждый домен только описывает, какие permissions относятся к его ресурсам.

Глобальной роли teacher нет. Преподавательский, олимпиадный или редакторский доступ выражается через membership/context + catalog permissions в соответствующем домене.

Learning Workspace permissions belong to the LMS domain. learning_group, learning_group_participant, training attempts and review sessions use lms.* permissions with assigned, organization or global scope. educator_profile remains identity-owned and uses identity permissions. Competitions permissions can grant access to registrations, submissions and results, but cannot grant ownership of the canonical «my students / my groups» roster.

Organization-context grants

Organization-local roles and permission grants use the same catalog permission keys as global/domain RBAC.

Grant может сужать доступ по:

  • identity_organization_id when used outside identity-owned tables;
  • team_id;
  • membership_id;
  • resource_type / resource_id.

Пример: task-bank.collections.manage.organization может быть выдан team или membership внутри конкретной организации, но сама подборка остаётся task-bank-owned.

Пример: lms.learning-groups.read.assigned может быть выдан преподавателю через Learning Workspace assignment. competitions.results.read.organization может дать организации доступ к опубликованным или разрешённым результатам её участников, но не к управлению learning group participants.

Запрещены локальные namespaces вроде:

  • org.*;
  • organization.*;
  • students.*;
  • olympiad.*;
  • problem_bank.*.
  • competition_group.* как источник прав на учебные группы.

Все permissions должны существовать в packages/permissions/catalog.ts; identity organization roles и grants не принимают произвольные строки.

Синхронизация каталога

Источник истины runtime-каталога permissions — packages/permissions/catalog.ts. Доменные permissions-matrix.md являются спецификацией требований и проходят сверку с TypeScript-каталогом в CI.

Правило синхронизации:

  • новое permission сначала добавляется в доменный permissions-matrix.md;
  • затем оно добавляется в packages/permissions/catalog.ts;
  • CI падает, если доменная матрица ссылается на permission, которого нет в catalog, или catalog содержит permission без владельца-домена;
  • service scopes (service:<permission>) генерируются из того же catalog.

Где описаны permissions конкретного домена

В каждом домене есть permissions-matrix.md со столбцами:

endpoint/action → permission → scope → actor context → audit

Пример строки:

EndpointPermissionScopeActor contextAudit
POST /api/v2/lms/enrollmentslms.enrollments.manageassigned/organization/globalpersonal/adminда

Проверка permission

Проверка делается в backend-сервисе домена в три шага:

  1. Аутентификация: токен валиден.
  2. Авторизация по permission: у пользователя есть нужный permission в нужном scope.
  3. Проверка контекста: для action в чужом контексте (X-Actor-Context) — сервис проверяет, что пользователь имеет право в этом контексте.

Реализация — общий пакет packages/permissions. NestJS guards: @RequirePermission('lms.courses.read', { scope: 'assigned' }).

Получение permissions для пользователя

Backend-сервис может:

  • использовать claims из access token (roles, permissions если есть);
  • запросить расширенные permissions через GET https://id.systematika.tld/api/v2/identity/admin/users/{id}/permissions с кешем 60 секунд;
  • проверить scope-зависимые permissions локально, если объект-ownership определяется самим доменом (например, lms знает, что assigned означает teacher_assignment).

Для Learning Workspace assigned вычисляется LMS по identity-owned educator_profile reference, learning_group и learning_group_participant. Для competitions assigned вычисляется competitions по review/check/venue/season access. Эти два assigned-context не смешиваются.

Каталог permissions

Каталог поддерживается централизованно: packages/permissions/catalog.ts содержит TypeScript-список всех permission-ключей. Каждый домен дополняет каталог при добавлении новых ресурсов и actions. Разработка с нуля: каталог — первая сущность, описанная при бутстрапе домена.

Audit

Любая операция, изменяющая защищённое состояние, логируется в audit:

  • actor_user_id
  • actor_context
  • permission_used
  • target_entity, target_id
  • before / after (для критичных)
  • request_id
  • created_at

Запрещено

  • Изобретать собственный формат permission в домене.
  • Делать проверку только на frontend.
  • Полагаться на роль вместо permission (роль — агрегат).
  • Жёстко зашивать роли в код домена.
  • Использовать manage там, где явно достаточно read.

Wildcard levels

  • Global wildcard * applies only to global/platform permissions and does not automatically grant organization-local owner role.
  • Organization wildcard * applies only inside one organization.
  • Team wildcard * applies only inside one team.
  • Break-glass global access to organization data requires explicit global permission and audit.

Связанные документы