From bd1b17b58987ac3f33ee2a8b87b0aa52d1358871 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Thu, 21 May 2026 18:35:50 +0200 Subject: [PATCH] test(grants): full coverate of /api/files and /api/folders --- tests/api/grants.hurl | 401 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 401 insertions(+) diff --git a/tests/api/grants.hurl b/tests/api/grants.hurl index d5378479..4bd1042c 100644 --- a/tests/api/grants.hurl +++ b/tests/api/grants.hurl @@ -301,3 +301,404 @@ Authorization: Bearer {{eve_token}} HTTP 200 [Asserts] jsonpath "$" count == 0 + + +# ════════════════════════════════════════════════════════════════════ +# PHASE 2 — Comprehensive permission coverage with fresh user "adam". +# ════════════════════════════════════════════════════════════════════ +# Exercises every engine-aware endpoint at each permission tier: +# +# no grant → all read/write/delete operations return 404 +# Viewer → read endpoints OK, modify/delete endpoints return 404 +# Editor → update + create + thumbnail-push OK, delete still 404 +# Admin → everything including delete +# +# Endpoints in scope (all routed through the AuthorizationEngine): +# Folders: /contents · /contents/paginated · /listing · /download (zip) +# · POST / · PUT /{id}/rename · PUT /{id}/move · DELETE /{id} +# Files: GET / · GET /{id} (download) +# · GET /{id}/metadata · GET /{id}/thumbnail/{size} +# · PUT /{id}/thumbnail/{size} (push, Update) +# · PUT /{id}/rename · PUT /{id}/move · DELETE /{id} +# · POST /upload (via folder has_permission) +# +# Listing endpoints that are still owner-scoped (GET /api/folders root, +# GET /api/folders/paginated) are NOT covered here — they don't +# reflect grants today and are tracked as separate cleanup work. + + +# ───────────────────────────────────────────────────────────── +# Step 20 — Create user adam (fresh, no relationship to alice's tree). +# ───────────────────────────────────────────────────────────── +POST {{base_url}}/api/admin/users +Authorization: Bearer {{alice_token}} +Content-Type: application/json +{ "username": "adam", "password": "AdamPassword1!", "email": "adam@example.com", "role": "user" } + +HTTP 201 +[Captures] +adam_user_id: jsonpath "$.id" + +POST {{base_url}}/api/auth/login +Content-Type: application/json +{ "username": "adam", "password": "AdamPassword1!" } + +HTTP 200 +[Captures] +adam_token: jsonpath "$.access_token" + + +# ───────────────────────────────────────────────────────────── +# Step 21 — Alice creates a fresh shareable folder, sub-folder, and +# uploads a JPEG (which the server auto-thumbnails). +# ───────────────────────────────────────────────────────────── +POST {{base_url}}/api/folders +Authorization: Bearer {{alice_token}} +Content-Type: application/json +{ "name": "perm-test-folder", "parent_id": "{{alice_home_id}}" } + +HTTP 201 +[Captures] +perm_folder_id: jsonpath "$.id" + +POST {{base_url}}/api/folders +Authorization: Bearer {{alice_token}} +Content-Type: application/json +{ "name": "perm-test-child", "parent_id": "{{perm_folder_id}}" } + +HTTP 201 +[Captures] +perm_child_id: jsonpath "$.id" + +POST {{base_url}}/api/files/upload +Authorization: Bearer {{alice_token}} +[MultipartFormData] +folder_id: {{perm_folder_id}} +file: file,fixtures/oxicloud-logo.jpg; image/jpeg + +HTTP 201 +[Captures] +perm_file_id: jsonpath "$.id" + + +# ════════════════════════════════════════════════════════════════════ +# Phase 2A — Adam has NO grant. Every engine-aware endpoint denies. +# ════════════════════════════════════════════════════════════════════ + +# ── Folder reads ───────────────────────────────────────────── +GET {{base_url}}/api/folders/{{perm_folder_id}}/contents +Authorization: Bearer {{adam_token}} + +HTTP 404 + +GET {{base_url}}/api/folders/{{perm_folder_id}}/contents/paginated +Authorization: Bearer {{adam_token}} + +HTTP 404 + +GET {{base_url}}/api/folders/{{perm_folder_id}}/listing +Authorization: Bearer {{adam_token}} + +HTTP 404 + +GET {{base_url}}/api/folders/{{perm_folder_id}}/download +Authorization: Bearer {{adam_token}} + +HTTP 404 + +# ── File reads ─────────────────────────────────────────────── +GET {{base_url}}/api/files?folder_id={{perm_folder_id}} +Authorization: Bearer {{adam_token}} + +HTTP 404 + +GET {{base_url}}/api/files/{{perm_file_id}} +Authorization: Bearer {{adam_token}} + +HTTP 404 + +GET {{base_url}}/api/files/{{perm_file_id}}/metadata +Authorization: Bearer {{adam_token}} + +HTTP 404 + +GET {{base_url}}/api/files/{{perm_file_id}}/thumbnail/icon +Authorization: Bearer {{adam_token}} + +HTTP 404 + +# ── Folder mutations ───────────────────────────────────────── +POST {{base_url}}/api/folders +Authorization: Bearer {{adam_token}} +Content-Type: application/json +{ "name": "adam-attack", "parent_id": "{{perm_folder_id}}" } + +HTTP 404 + +PUT {{base_url}}/api/folders/{{perm_folder_id}}/rename +Authorization: Bearer {{adam_token}} +Content-Type: application/json +{ "name": "adam-rename-attempt" } + +HTTP 404 + +DELETE {{base_url}}/api/folders/{{perm_folder_id}} +Authorization: Bearer {{adam_token}} + +HTTP 404 + +# ── File mutations ─────────────────────────────────────────── +POST {{base_url}}/api/files/upload +Authorization: Bearer {{adam_token}} +[MultipartFormData] +folder_id: {{perm_folder_id}} +file: file,fixtures/hello.txt; text/plain + +HTTP 404 + +PUT {{base_url}}/api/files/{{perm_file_id}}/rename +Authorization: Bearer {{adam_token}} +Content-Type: application/json +{ "name": "adam-file-rename" } + +HTTP 404 + +PUT {{base_url}}/api/files/{{perm_file_id}}/thumbnail/icon +Authorization: Bearer {{adam_token}} +Content-Type: image/png +file,fixtures/blue-image.png; + +HTTP 404 + +DELETE {{base_url}}/api/files/{{perm_file_id}} +Authorization: Bearer {{adam_token}} + +HTTP 404 + + +# ════════════════════════════════════════════════════════════════════ +# Phase 2B — Alice grants adam Viewer. Read OK, mutate/delete denied. +# ════════════════════════════════════════════════════════════════════ +POST {{base_url}}/api/grants +Authorization: Bearer {{alice_token}} +Content-Type: application/json +{ + "subject": { "type": "user", "id": "{{adam_user_id}}" }, + "resource": { "type": "folder", "id": "{{perm_folder_id}}" }, + "role": "viewer" +} + +HTTP 201 + +# ── Read endpoints now succeed ────────────────────────────── +GET {{base_url}}/api/folders/{{perm_folder_id}}/contents +Authorization: Bearer {{adam_token}} + +HTTP 200 +[Asserts] +jsonpath "$" count == 1 +jsonpath "$[0].id" == "{{perm_child_id}}" + +GET {{base_url}}/api/folders/{{perm_folder_id}}/contents/paginated +Authorization: Bearer {{adam_token}} + +HTTP 200 + +GET {{base_url}}/api/folders/{{perm_folder_id}}/listing +Authorization: Bearer {{adam_token}} + +HTTP 200 + +GET {{base_url}}/api/folders/{{perm_folder_id}}/download +Authorization: Bearer {{adam_token}} + +HTTP 200 +[Asserts] +header "Content-Type" contains "zip" + +GET {{base_url}}/api/files?folder_id={{perm_folder_id}} +Authorization: Bearer {{adam_token}} + +HTTP 200 +[Asserts] +jsonpath "$" count == 1 +jsonpath "$[0].id" == "{{perm_file_id}}" + +GET {{base_url}}/api/files/{{perm_file_id}} +Authorization: Bearer {{adam_token}} + +HTTP 200 + +GET {{base_url}}/api/files/{{perm_file_id}}/metadata +Authorization: Bearer {{adam_token}} + +HTTP 200 + +GET {{base_url}}/api/files/{{perm_file_id}}/thumbnail/icon +Authorization: Bearer {{adam_token}} + +HTTP 200 +[Asserts] +header "Content-Type" startsWith "image/" + +# ── Cascading: child folder also readable via parent's grant ─ +GET {{base_url}}/api/folders/{{perm_child_id}}/contents +Authorization: Bearer {{adam_token}} + +HTTP 200 + +# ── Mutations still denied (Viewer has no Update/Create/Delete) ─ +POST {{base_url}}/api/folders +Authorization: Bearer {{adam_token}} +Content-Type: application/json +{ "name": "adam-attack-2", "parent_id": "{{perm_folder_id}}" } + +HTTP 404 + +PUT {{base_url}}/api/folders/{{perm_folder_id}}/rename +Authorization: Bearer {{adam_token}} +Content-Type: application/json +{ "name": "adam-rename-as-viewer" } + +HTTP 404 + +PUT {{base_url}}/api/files/{{perm_file_id}}/rename +Authorization: Bearer {{adam_token}} +Content-Type: application/json +{ "name": "adam-file-rename-as-viewer" } + +HTTP 404 + +PUT {{base_url}}/api/files/{{perm_file_id}}/thumbnail/icon +Authorization: Bearer {{adam_token}} +Content-Type: image/png +file,fixtures/blue-image.png; + +HTTP 404 + +POST {{base_url}}/api/files/upload +Authorization: Bearer {{adam_token}} +[MultipartFormData] +folder_id: {{perm_folder_id}} +file: file,fixtures/hello.txt; text/plain + +HTTP 404 + +DELETE {{base_url}}/api/files/{{perm_file_id}} +Authorization: Bearer {{adam_token}} + +HTTP 404 + +DELETE {{base_url}}/api/folders/{{perm_folder_id}} +Authorization: Bearer {{adam_token}} + +HTTP 404 + + +# ════════════════════════════════════════════════════════════════════ +# Phase 2C — Promote adam to Editor (read + comment + create + update). +# Create + Update endpoints now succeed; Delete still denied. +# ════════════════════════════════════════════════════════════════════ +PUT {{base_url}}/api/grants/role +Authorization: Bearer {{alice_token}} +Content-Type: application/json +{ + "subject": { "type": "user", "id": "{{adam_user_id}}" }, + "resource": { "type": "folder", "id": "{{perm_folder_id}}" }, + "role": "editor" +} + +HTTP 200 + +# ── Update succeeds ───────────────────────────────────────── +PUT {{base_url}}/api/folders/{{perm_folder_id}}/rename +Authorization: Bearer {{adam_token}} +Content-Type: application/json +{ "name": "renamed-by-adam-as-editor" } + +HTTP 200 + +PUT {{base_url}}/api/files/{{perm_file_id}}/rename +Authorization: Bearer {{adam_token}} +Content-Type: application/json +{ "name": "adam-renamed-logo.jpg" } + +HTTP 200 + +# ── Thumbnail push (Update) succeeds ──────────────────────── +PUT {{base_url}}/api/files/{{perm_file_id}}/thumbnail/preview +Authorization: Bearer {{adam_token}} +Content-Type: image/png +file,fixtures/blue-image.png; + +HTTP 201 + +# ── Create succeeds ───────────────────────────────────────── +POST {{base_url}}/api/folders +Authorization: Bearer {{adam_token}} +Content-Type: application/json +{ "name": "adam-created-child", "parent_id": "{{perm_folder_id}}" } + +HTTP 201 + +POST {{base_url}}/api/files/upload +Authorization: Bearer {{adam_token}} +[MultipartFormData] +folder_id: {{perm_folder_id}} +file: file,fixtures/hello.txt; text/plain + +HTTP 201 + +# ── Delete still denied (Editor excludes Delete) ──────────── +DELETE {{base_url}}/api/files/{{perm_file_id}} +Authorization: Bearer {{adam_token}} + +HTTP 404 + +DELETE {{base_url}}/api/folders/{{perm_folder_id}} +Authorization: Bearer {{adam_token}} + +HTTP 404 + + +# ════════════════════════════════════════════════════════════════════ +# Phase 2D — Promote adam to Admin (all 6 permissions). Delete OK. +# ════════════════════════════════════════════════════════════════════ +PUT {{base_url}}/api/grants/role +Authorization: Bearer {{alice_token}} +Content-Type: application/json +{ + "subject": { "type": "user", "id": "{{adam_user_id}}" }, + "resource": { "type": "folder", "id": "{{perm_folder_id}}" }, + "role": "admin" +} + +HTTP 200 + +DELETE {{base_url}}/api/files/{{perm_file_id}} +Authorization: Bearer {{adam_token}} + +HTTP 204 + + +# ════════════════════════════════════════════════════════════════════ +# Phase 2E — Lifecycle cleanup. Alice (still the owner) trashes & +# empties; the trigger removes all access_grants rows. +# ════════════════════════════════════════════════════════════════════ +DELETE {{base_url}}/api/folders/{{perm_folder_id}} +Authorization: Bearer {{alice_token}} + +HTTP 204 + +DELETE {{base_url}}/api/trash/empty +Authorization: Bearer {{alice_token}} + +HTTP 200 + +# Adam's incoming list is empty. +GET {{base_url}}/api/grants/incoming +Authorization: Bearer {{adam_token}} + +HTTP 200 +[Asserts] +jsonpath "$" count == 0