feat(access): гранулярные доступы (access_grants)
AccessGrantRepository:
- CRUD (List/Create/Get/Delete) с UPSERT на (resource, subject) для
идемпотентности повторных grant'ов.
- ResolveVisibleResourceIDs(viewer ViewerContext) — для данного юзера
возвращает DISTINCT set resource_id'шников, выданных через любой из
subject_type: 'public' OR 'user'==viewer OR 'role'∈viewer.RoleIDs OR
'department'==viewer.DepartmentID OR 'position'==viewer.PositionID.
ViewerContext собирается из X-User-Roles/Department-Id/Position-Id
headers'ов (portal-gateway прокидывает после JWT-валидации).
AccessGrantHandler:
- GET /access/{resourceType}/{resourceId} — list (owner-only).
- POST /access/{resourceType}/{resourceId} — выдать (UPSERT).
- DELETE /access/{resourceType}/{resourceId}/grants/{grantId} —
отозвать. resourceType/Id дублируются в URL'е для cross-check'а
чтобы owner ресурса A не мог удалить grant ресурса B по grantId.
Интеграция в List'ах:
- TestHandler.List: ?mine=true работает как было; без mine видны
published + дозалив unpublished, выданных через access_grants.
- CourseHandler.List: то же поведение зеркально.
Семантика union'а: «published all + grant-only». Это backward-compat
(старые published продолжают быть видны всем), при этом HR может
явно выдать draft-ресурс конкретному юзеру/роли без публикации.
helpers.go: viewerContextFromHeaders — парсит X-User-Roles (CSV),
X-User-Department-Id, X-User-Position-Id; невалидные/пустые → default.
Wire-up: accessRepo внедрён в Test/Course handler'ы; accessH
зарегистрирован вместо предыдущей 501-заглушки.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -71,13 +71,15 @@ func main() {
|
||||
courseRepo := repository.NewCourseRepository(pool)
|
||||
lessonRepo := repository.NewLessonRepository(pool)
|
||||
publicTokenRepo := repository.NewPublicTokenRepository(pool)
|
||||
accessRepo := repository.NewAccessGrantRepository(pool)
|
||||
|
||||
healthH := handler.NewHealthHandler(pool)
|
||||
testH := handler.NewTestHandler(testRepo)
|
||||
testH := handler.NewTestHandler(testRepo, accessRepo)
|
||||
attemptH := handler.NewAttemptHandler(attemptRepo, testRepo)
|
||||
courseH := handler.NewCourseHandler(courseRepo)
|
||||
courseH := handler.NewCourseHandler(courseRepo, accessRepo)
|
||||
lessonH := handler.NewLessonHandler(lessonRepo, courseRepo, store)
|
||||
publicTokenH := handler.NewPublicTokenHandler(publicTokenRepo, testRepo, courseRepo, attemptRepo)
|
||||
accessH := handler.NewAccessGrantHandler(accessRepo, testRepo, courseRepo)
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(chimw.RequestID)
|
||||
@@ -137,7 +139,12 @@ func main() {
|
||||
r.Post("/lessons/{id}/video", lessonH.UploadVideo)
|
||||
r.Get("/lessons/{id}/video/stream", lessonH.StreamVideo)
|
||||
r.Delete("/lessons/{id}/video", lessonH.DeleteVideo)
|
||||
r.HandleFunc("/access/{resourceType}/{resourceId}", notImplemented)
|
||||
// Access grants — гранулярные доступы (user/role/department/position/public).
|
||||
// Управляет владелец ресурса; SubjectIDs матчатся по X-User-Roles/
|
||||
// Department/Position-headers'ам, прокидываемым portal-gateway'ом.
|
||||
r.Get("/access/{resourceType}/{resourceId}", accessH.List)
|
||||
r.Post("/access/{resourceType}/{resourceId}", accessH.Create)
|
||||
r.Delete("/access/{resourceType}/{resourceId}/grants/{grantId}", accessH.Delete)
|
||||
|
||||
// Public tokens — HR-side: создать ссылку для кандидата, посмотреть
|
||||
// список, отозвать. Сам прохождение тестa по токену — в /public ниже.
|
||||
|
||||
Reference in New Issue
Block a user