KsaiLab
06 Authorization

Access groups и учебный ABAC boundary

Access groups и учебный ABAC boundary

Статус документа

Этот документ синхронизирует публичную документацию KSAILab с backend MR ksailab-app-backend!30.

По состоянию на 2026-04-28 backend зафиксировал новую source-of-truth границу между учебными группами, группами доступа, permission registry, учебным контентом, банком вопросов, тестами и файлами. Документ намеренно описывает текущий runtime contract, а не будущий course/custom-role/admin UI target.

Три разных типа групп

В публичной документации слово group нужно читать только вместе с контекстом:

КонтурЧто этоBackend surfaceЧто важно
Учебные группыProduct/education groups, roster, onboarding, teacher/student membership/api/v1/groups/management*, совместимые member endpoints /api/v1/groups/students* и /api/v1/groups/teachers*Это рабочий учебный контур, а не permissions CRUD
Группы доступаAccess-control entity для назначения прикладного доступа/api/v1/access-groupsЭто отдельный access-assignment/admin surface
permissions/groupsRead-only snapshot/registry surface для permission-side metadataGET /api/v1/permissions/catalog, GET /api/v1/permissions/role-mappings, GET /api/v1/permissions/bundlesЭто не CRUD ни для учебных групп, ни для access groups

Legacy split routes_old.py / routes_new.py больше не является активной границей groups router. Каноническая учебная поверхность находится в groups management router и должна документироваться как /api/v1/groups/management*.

Runtime access model

GET /api/v1/auth/me остаётся единственным bootstrap surface для human/browser UI. Response публикует отдельный access block рядом с principal, classification, realm_roles, bundles и effective_capabilities.

access_assignment_mode нужно читать так:

  • legacy_role — переходный baseline, где прикладный доступ ещё выводится из coarse system role;
  • access_group — рабочий доступ выдан через access-group assignment;
  • unassigned — identity/session/bootstrap есть, но прикладного доступа к рабочим ресурсам нет.

unassigned не является ошибкой login flow. Это валидное состояние identity до выдачи доступа. Пользователь может пройти bootstrap, но content, groups, question bank, tests и files должны возвращать 403, если для действия нужен рабочий access scope.

System roles admin, teacher, student остаются coarse classification и starter baseline. Их нельзя описывать как editable business roles или как замену access-group/bundle/custom-role модели.

Учебный content boundary

Отдельного /courses API в текущем backend runtime пока нет. Course-like модель сейчас живёт через topic-based ancestry:

topics -> sections -> subsections -> tests

Пока dedicated course domain/API не появился, topic_id является каноническим идентификатором course-like ресурса для UI, тестов и интеграций.

Student access проходит через активный learning scope:

  • пользователь должен быть student;
  • студент должен быть активным участником учебной группы;
  • учебная группа должна быть активной;
  • связь group_topics должна быть активной;
  • тема и связанные section/subsection/test ресурсы не должны быть архивированы.

Teacher access для content/test management не должен опираться только на coarse role. Backend проверяет resource scope через:

  • creator темы;
  • активного co-author темы;
  • ancestry от section/subsection/test к теме, где teacher имеет доступ.

admin сохраняет platform-wide доступ. No-access или unassigned principal получает 403 до resource action.

Question bank и tests boundary

Question bank и test flows теперь связаны с content scope:

  • вопрос можно создавать и менять только внутри темы/занятия, доступных текущему admin или teacher;
  • teacher управляет только вопросами тем, где он creator или активный topic author;
  • нельзя привязать к тесту wrong-scope question, вопрос из другой темы или архивированный вопрос;
  • generated/final tests строятся только из eligible active questions в нужном topic/content scope;
  • итоговые тесты используют существующие вопросы через связи test questions, а не старую модель клонирования вопроса в test-local сущность.

Student attempt payload намеренно безопасный. Для студента не должны ожидаться:

  • correct_answer;
  • индексы правильных ответов;
  • внутренняя metadata генерации или randomization;
  • авторские поля;
  • archive/internal status.

Frontend должен считать отсутствие этих полей нормой и не строить UI на скрытых admin/teacher-only данных.

File access policy

Object path или raw MinIO URL сам по себе не является правом доступа. Storage identifier получает смысл только через связанный учебный ресурс:

  • Topic.image;
  • Question.image_url;
  • Subsection.file_path;
  • Subsection.content с minio://...;
  • Subsection.slides.

info, proxy, stream, delete и presigned flows должны проходить resource-scope authorization до выдачи URL или чтения объекта:

  • admin остаётся platform-wide;
  • teacher читает файл только через доступную ему тему как creator/co-author;
  • student читает, проксирует или стримит файл только через назначенный активный учебный content scope;
  • teacher upload требует явный topic_id или subsection_id;
  • delete файла, который уже используется в content/question/subsection, должен сначала пройти detach flow и может вернуть 409 Conflict;
  • teacher не должен удалять untracked object, потому что для standalone object ownership сейчас нет отдельной модели.

UI не должен считать старый raw URL или сохранённый object path достаточным подтверждением доступа. Для обновления ссылок и отображения файлов нужно идти через backend resource flow и быть готовым к 403 или 409.

Live API smoke/e2e

Backend MR !30 добавил opt-in live API harness для deployed API. Его нельзя запускать как обычный публичный docs check, потому что нужен свежий admin session cookie.

Read-only smoke:

.\scripts\run_live_api_tests.ps1 -SessionCookie "ksailab_session=<fresh-admin-session>"

Mutation flow:

.\scripts\run_live_api_tests.ps1 -SessionCookie "ksailab_session=<fresh-admin-session>" -Mutation

Operational rules:

  • cookie хранить только локально или в ENV, например KS_SESSION_COOKIE;
  • cookie, receipts и временные пароли не коммитить;
  • credential_receipt и временный пароль печатать только при явном -RevealReceipts;
  • без -Mutation harness должен оставаться read-only smoke;
  • KS_API_BASE_URL можно переопределить для стенда, но default backend script указывает на deployed API.

Source mapping

Backend source of truth для этой синхронизации:

Follow-up, который нельзя выдавать за landed

  • отдельный /courses API;
  • custom-role CRUD и editable system roles;
  • permission-side write UI для access assignments/bundle assignments;
  • frontend dedicated CRUD поверх /api/v1/access-groups;
  • hard delete/deprovision для Keycloak-managed human accounts как обычный UI action.

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