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/groups | Read-only snapshot/registry surface для permission-side metadata | GET /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;- без
-Mutationharness должен оставаться read-only smoke; KS_API_BASE_URLможно переопределить для стенда, но default backend script указывает на deployed API.
Source mapping
Backend source of truth для этой синхронизации:
docs/auth-capability-bootstrap-contract.mddocs/keycloak-onboarding-provisioning-flow.mddocs/local-development.mddocs/content-course-access-contract.mddocs/question-bank-content-access-abac.mddocs/file-access-policy.mddocs/topic_access_access_control_analysis.mdksailab-app-backend!30
Follow-up, который нельзя выдавать за landed
- отдельный
/coursesAPI; - 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.