security(nextcloud): chroot-aware display paths + recent race fix
strip_chroot_prefix replaces the hardcoded "Personal/" strip
in NC trashbin PROPFIND, OCS unified search, and REPORT
(favorites + search). Handles composed chroots, drops
cross-chroot items instead of surfacing malformed paths, and
fixes the leading-slash mismatch (FolderDto path has '/', DB
paths don't) that silently dropped every NC trashbin item
post-D3. OCS keeps a first-segment fallback (results
legitimately span drives, no single chroot).
uploads_handler switches to nc_to_internal_path(chroot, …)
for the two remaining hardcoded "Personal/" sites, closing
the D1 TODO markers.
RecentService::record_item_access is split from a new
record_item_access_internal (no authz) used by
RecentRecordingHook. Round 1's authz.require widened the
tokio::spawn race past tests/api/recent.hurl step 7; the
internal path skips the redundant Read gate — upstream
_with_perms already enforced it.
Tests: 8 unit tests pin strip_chroot_prefix (leading slash,
composed chroots, sibling-leak rejection, partial-prefix,
empty-chroot). drives_membership.hurl step 21b/22b cover
Editor upload → 201 / Viewer upload → 404 fresh + overwrite
with fixture cleanup at 30c. test_nc_move_copy_delete_trash
K1 pins the actual original-location value.
This commit is contained in:
@@ -511,6 +511,26 @@ HTTP 200
|
||||
jsonpath "$[*].id" contains {{team_drive_id}}
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 21b — Upload gate by role (post-Drive AuthZ audit Round 2).
|
||||
# Bob is Editor on team_drive; `POST /api/files/upload`
|
||||
# targeting team_root_folder_id should succeed. This is
|
||||
# the REST-side counterpart of the WebDAV/NC PUT chain
|
||||
# hardened by `update_file_streaming_with_perms`. If
|
||||
# this fails, the whole role-bundle → Permission::Create
|
||||
# wiring is broken.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/files/upload
|
||||
Authorization: Bearer {{bob_token}}
|
||||
[MultipartFormData]
|
||||
folder_id: {{team_root_folder_id}}
|
||||
file: file,fixtures/hello.txt; text/plain
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
bob_editor_upload_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 22 — Higher role wins: Bob now ALSO gets a Viewer direct
|
||||
# grant (would lower his bundle). The collapsed caller_role
|
||||
@@ -537,6 +557,50 @@ HTTP 200
|
||||
jsonpath "$[*].id" contains {{team_drive_id}}
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 22b — Viewer CANNOT upload into a shared drive.
|
||||
# Post-Drive AuthZ audit Round 2: the create branch of
|
||||
# `update_file_streaming_with_perms` requires
|
||||
# `Permission::Create` on the parent folder — bundled
|
||||
# with `owner`/`editor`/`contributor` role_grants only,
|
||||
# NOT with `viewer`. `POST /api/files/upload` shares the
|
||||
# same `save_file_with_blob` gate, so a Viewer probe
|
||||
# must land 404 (anti-enum: same shape as no-such-folder)
|
||||
# + `authz.denied` audit line. Also verify the batch /
|
||||
# overwrite paths refuse — the whole chain from
|
||||
# drive-membership to file write is exercised here.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
# 22b.i — Fresh file: 404.
|
||||
POST {{base_url}}/api/files/upload
|
||||
Authorization: Bearer {{bob_token}}
|
||||
[MultipartFormData]
|
||||
folder_id: {{team_root_folder_id}}
|
||||
file: file,fixtures/hello.txt; text/plain
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# 22b.ii — Overwrite attempt on the Editor-era upload: still 404.
|
||||
# `save_file_with_blob` catches the duplicate name at the
|
||||
# `Create`-permission check before the upsert races (which
|
||||
# would otherwise 409). The audit shape stays 404.
|
||||
POST {{base_url}}/api/files/upload
|
||||
Authorization: Bearer {{bob_token}}
|
||||
[MultipartFormData]
|
||||
folder_id: {{team_root_folder_id}}
|
||||
file: file,fixtures/hello.txt; text/plain
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# 22b.iii — Alice's Editor-era file is untouched.
|
||||
GET {{base_url}}/api/files/{{bob_editor_upload_id}}
|
||||
Authorization: Bearer {{alice_token}}
|
||||
|
||||
HTTP 200
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Per-role mutation matrix — what every role can / can't do
|
||||
# =============================================================
|
||||
@@ -845,15 +909,22 @@ HTTP 409
|
||||
|
||||
|
||||
# 30c — Clear the lingering content (the Editor-created folder from
|
||||
# Step 27). Delete via the regular folder endpoint so the row
|
||||
# lands in trash, not the live tree; `is_empty` excludes
|
||||
# trashed rows so a populated trash bin is allowed.
|
||||
# Step 27 and the Editor-era file from Step 21b). Delete via
|
||||
# the regular endpoints so rows land in trash, not the live
|
||||
# tree; `is_empty` excludes trashed rows so a populated trash
|
||||
# bin is allowed.
|
||||
DELETE {{base_url}}/api/folders/{{editor_created_folder_id}}
|
||||
Authorization: Bearer {{alice_token}}
|
||||
|
||||
HTTP 204
|
||||
|
||||
|
||||
DELETE {{base_url}}/api/files/{{bob_editor_upload_id}}
|
||||
Authorization: Bearer {{alice_token}}
|
||||
|
||||
HTTP 204
|
||||
|
||||
|
||||
# 30d — Owner on an empty drive → 204.
|
||||
DELETE {{base_url}}/api/drives/{{team_drive_id}}
|
||||
Authorization: Bearer {{alice_token}}
|
||||
|
||||
Reference in New Issue
Block a user