у записи появился владелец: чужую больше не отдают
- колонка `owner` связью с `users` в обеих коллекциях новым шагом схемы `202608140001`; чтение задачи сужено владельцем, и чужая, ничья и несуществующая дают один ответ; правило просмотра файлов сужено им же - приём по HTTP берёт владельца из сессии, а предъявителя без учётной записи пользователя отвергает до чтения тела: позже пришлось бы убирать уложенный файл, а уборки файлов сервис не умеет. Выборка воркера владельцем не сужается - удаление учётной записи с записями отвергается стражем, и вешает его сама сборка хранилища: сборка, забывшая его позвать, теряла защиту молча
This commit is contained in:
@@ -186,3 +186,37 @@ pb_data/storage/<коллекция>/<запись>/<имя>_<10 случайн
|
||||
способов:
|
||||
вместе с панелью отказ выбрасывал бы уход CGO и встроенное резервное
|
||||
копирование, которых у сервиса-архива нет никаких.
|
||||
|
||||
## Разграничение по владельцу: что выяснилось при реализации
|
||||
|
||||
Дописано 2026-08-14 задачей `record-ownership`. Все находки ниже получены одним
|
||||
способом: чтением исходников `pocketbase@v0.39.10` из кеша модулей и прогонами
|
||||
против настоящего хранилища на временном каталоге — в ходе ревью того change.
|
||||
|
||||
**Связь с выключенным каскадом не удерживает целостность при удалении.**
|
||||
`core/record_model.go`, `deleteRefRecords`: при `CascadeDelete = false` и
|
||||
необязательном поле хранилище **вынимает** идентификатор из поля связи и
|
||||
сохраняет запись через `SaveNoValidate`. То есть «не уносить записи следом» и
|
||||
«сохранить у них владельца» — разные вещи, и связь даёт только первое.
|
||||
|
||||
**Наружу проходит только ошибка роутера.** `apis/record_crud.go` заворачивает
|
||||
отказ хука в `firstApiError(err, e.BadRequestError("Failed to delete record. Make
|
||||
sure that the record is not part of a required relation reference.", err))`, а
|
||||
`firstApiError` берёт первый аргумент, только если он `*router.ApiError`. Обычная
|
||||
ошибка из хука до ответа не доезжает вовсе, и спрашивающий получает библиотечную
|
||||
подсказку про обязательную связь — в нашем случае указывающую не на ту связь.
|
||||
|
||||
**`apis/file.go` выдаёт токен файла на предъявителя, а не на файл.** О файле при
|
||||
выдаче он не спрашивает. Владельца судит переход по ссылке: правило просмотра
|
||||
коллекции проверяет защищённое поле файла по учётной записи **из токена**. Значит
|
||||
чужой токен получить можно всегда, а скачать по нему чужой файл — нет.
|
||||
|
||||
**Проверка сессии с именем коллекции отвечает `403`, а не `401`.**
|
||||
`apis.RequireAuth("users")` пускает только запись названной коллекции; предъявитель
|
||||
из другой — например, владелец панели — узнан, но не годится, и код отказа это
|
||||
различает.
|
||||
|
||||
**Связь в SQLite лежит пустой строкой, а не `NULL`.** `RelationField.ColumnType`
|
||||
даёт `TEXT DEFAULT '' NOT NULL`; сырой запрос и чтение через запись коллекции
|
||||
совпадают побайтово. «Умолчания у колонки нет» верно по замыслу — пустое значение
|
||||
не совпадает ни с кем, — но не буквально на уровне схемы.
|
||||
|
||||
Reference in New Issue
Block a user