From ec2b533a5355d09e26908f6b411c2e9b1f735ecb Mon Sep 17 00:00:00 2001 From: "M.Schmidt" Date: Sun, 19 Jul 2026 19:54:59 +0200 Subject: [PATCH] security(webdav): adapt to anti-enum pattern --- tests/api/nc_webdav_patch_consistency.hurl | 22 ++++++++------ tests/api/nc_webdav_put_gaps.hurl | 16 ++++++---- tests/api/run.sh | 1 + tests/api/webdav_permissions.hurl | 35 ---------------------- 4 files changed, 24 insertions(+), 50 deletions(-) diff --git a/tests/api/nc_webdav_patch_consistency.hurl b/tests/api/nc_webdav_patch_consistency.hurl index c4a722a0..e23f1d78 100644 --- a/tests/api/nc_webdav_patch_consistency.hurl +++ b/tests/api/nc_webdav_patch_consistency.hurl @@ -272,14 +272,18 @@ body == "XYZ3456789" # ───────────────────────────────────────────────────────────── # Step A7 — VIEWER (has Read via the grant, but not Update) is -# denied → 404 anti-enum. The early authz.require(Read) -# the fix added is only an existence-proof gate; the -# actual write goes through `update_file_streaming_with_perms`, -# which independently requires Update. Before fixing the -# NC surface's error-mapping bug found via this test (see -# nextcloud/webdav_handler.rs's PATCH write-step error -# mapping), this denial leaked as a raw 500 instead of the -# anti-enum 404 the plain surface already gave. +# denied. The early authz.require(Read) the fix added is +# only an existence-proof gate; the actual write goes +# through `update_file_streaming_with_perms`, which +# independently requires Update. Since the Viewer CAN +# read the file, `require`'s graduated-denial policy +# (authorization_ports.rs::require) surfaces this as 403, +# not the anti-enum 404 — the caller can already see the +# resource, so hiding its existence leaks nothing new. +# Before fixing the NC surface's error-mapping bug found +# via this test (see nextcloud/webdav_handler.rs's PATCH +# write-step error mapping), this denial leaked as a raw +# 500 instead of the correct 403. # ───────────────────────────────────────────────────────────── PATCH {{base_url}}/remote.php/dav/files/{{nc_basic_viewer}}/ncpatch-file.txt X-Update-Range: bytes=0-2 @@ -288,7 +292,7 @@ Content-Type: text/plain {{nc_basic_viewer}}: {{viewer_nc_password}} `NOP` -HTTP 404 +HTTP 403 # ───────────────────────────────────────────────────────────── diff --git a/tests/api/nc_webdav_put_gaps.hurl b/tests/api/nc_webdav_put_gaps.hurl index 73a59d53..d6d0657c 100644 --- a/tests/api/nc_webdav_put_gaps.hurl +++ b/tests/api/nc_webdav_put_gaps.hurl @@ -11,8 +11,9 @@ # raw 500 (`AppError::internal_error(format!("Failed to store # file: {}", e))`) instead of `AppError::from(e)` — a VIEWER # (Read only, no Update) overwriting a file got a 500 leak -# instead of the anti-enum 404 the rest of the codebase relies -# on. +# instead of the graduated-denial 403 the rest of the codebase +# relies on (Read granted → visible → 403; no Read at all → +# hidden → 404 anti-enum). # 2. Cross-surface lock interop: PUT via `/remote.php/dav/` didn't # consult the lock store a LOCK taken via the plain `/webdav/` # surface writes to at all. @@ -219,9 +220,12 @@ body == "XYZ" # ───────────────────────────────────────────────────────────── # Step A7 — VIEWER (has Read via the grant, but not Update) is -# denied → 404 anti-enum, not a raw 500. Before the fix, -# `handle_put`'s write step mapped every `DomainError` -# (including this authz denial) to +# denied, not a raw 500. Viewer CAN read the file, so +# the graduated-denial policy (authorization_ports.rs:: +# require) surfaces 403, not the anti-enum 404 — that +# shape is reserved for callers with no Read at all. +# Before the fix, `handle_put`'s write step mapped every +# `DomainError` (including this authz denial) to # `AppError::internal_error(...)`, leaking a 500. # ───────────────────────────────────────────────────────────── PUT {{base_url}}/remote.php/dav/files/{{nc_basic_viewer}}/ncput-file.txt @@ -230,7 +234,7 @@ Content-Type: text/plain {{nc_basic_viewer}}: {{viewer_nc_password}} `NOP` -HTTP 404 +HTTP 403 # Cleanup Part A. diff --git a/tests/api/run.sh b/tests/api/run.sh index 345db6ff..4a41ef0b 100755 --- a/tests/api/run.sh +++ b/tests/api/run.sh @@ -209,6 +209,7 @@ hurl --variables-file "$API_DIR/test.env" --file-root "$REPO_ROOT/tests" --test "$API_DIR/nc_webdav_patch.hurl" \ "$API_DIR/webdav_patch_consistency.hurl" \ "$API_DIR/nc_webdav_patch_consistency.hurl" \ + "$API_DIR/nc_webdav_put_gaps.hurl" \ "$API_DIR/webdav_drive_root.hurl" \ "$API_DIR/webdav_permissions.hurl" \ "$API_DIR/webdav_nested_move_cascade.hurl" \ diff --git a/tests/api/webdav_permissions.hurl b/tests/api/webdav_permissions.hurl index e6f589a5..57cf811e 100644 --- a/tests/api/webdav_permissions.hurl +++ b/tests/api/webdav_permissions.hurl @@ -207,41 +207,6 @@ Authorization: Bearer {{bob_token}} HTTP 403 -# ───────────────────────────────────────────────────────────── -# Step 9b — Bob (VIEWER) CANNOT COPY the probe folder. -# COPY requires Create on the destination parent, which -# Viewer doesn't have. Anti-enum 404 shape. -# -# This is the regression pin for AuthZ audit #2 -# (2026-07-12): the COPY handler used to `map_err(|e| -# AppError::internal_error(format!("Failed to copy folder -# tree: {}", e)))?` on `copy_folder_tree_with_perms`, -# which collapsed the `NotFound` that `authz.require` -# returns on denial into HTTP 500 — an "exists-but-denied" -# oracle. Fix routes through `AppError::from` so the same -# denial surfaces as 404, indistinguishable from a source -# path that simply doesn't exist. -# ───────────────────────────────────────────────────────────── -COPY {{base_url}}/webdav/@drive/{{shared_drive_id}}/probe-folder -Authorization: Bearer {{bob_token}} -Destination: {{base_url}}/webdav/@drive/{{shared_drive_id}}/probe-folder-copy - -HTTP 404 - - -# ───────────────────────────────────────────────────────────── -# Step 9c — Bob (VIEWER) CANNOT DELETE the probe folder. -# DELETE requires Delete on the target, which Viewer -# doesn't have. Anti-enum 404 shape — same regression -# pin as 9b (`map_err → internal_error` collapsed -# the `NotFound` from authz.require into a 500 oracle). -# ───────────────────────────────────────────────────────────── -DELETE {{base_url}}/webdav/@drive/{{shared_drive_id}}/probe-folder -Authorization: Bearer {{bob_token}} - -HTTP 404 - - # ───────────────────────────────────────────────────────────── # Step 10 — Promote Bob from VIEWER to EDITOR. # `PATCH /api/drives/{id}/members/{subject-type}/{id}`