feat: restrict backup archive download to primary admin and hide admin-hidden projects from other admins
DevPlace CI / test (push) Failing after 22m57s

- Add `get_admin_uids()` and `get_primary_admin_uid()` to database.py for resolving the earliest-created admin
- Modify `can_view_project()` in content.py so a project hidden by an admin is invisible to other admins (both web UI and REST API)
- Update `_download_url()` and `_backup_payload()` in admin/backups.py to accept a `can_download` flag, gating the download endpoint with `is_primary_admin()`
- Remove `role` from `_user_facts()` in docs_live.py to avoid leaking admin status in live docs
- Update doc summaries in docs_api.py to reflect the new admin-visibility and backup-download semantics
This commit is contained in:
2026-06-17 14:08:28 +00:00
parent 6b5347103b
commit 0a554ebc32
71 changed files with 1868 additions and 527 deletions
@@ -1,8 +1,52 @@
# retoor <retoor@molodetz.nl>
import time
import requests
from tests.conftest import BASE_URL
from devplacepy.database import get_table, refresh_snapshot
from devplacepy.utils import clear_user_cache
_counter_search = [0]
def _signup_search():
_counter_search[0] += 1
name = f"cas{int(time.time() * 1000)}{_counter_search[0]}"
requests.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
},
allow_redirects=True,
)
row = get_table("users").find_one(username=name)
return name, row["uid"], row["api_key"]
def _make_admin_search():
name, uid, key = _signup_search()
get_table("users").update({"uid": uid, "role": "Admin"}, ["uid"])
clear_user_cache(uid)
return name, uid, key
def _create_private_project(key, title):
r = requests.post(
f"{BASE_URL}/projects/create",
headers={"X-API-KEY": key, "Accept": "application/json"},
data={
"title": title,
"description": "search test",
"project_type": "software",
"status": "In Development",
"is_private": "on",
},
)
assert r.status_code == 200, r.text
return r.json()["data"]["slug"]
def _admin_session():
@@ -33,3 +77,21 @@ def test_project_search_empty_query(app_server, seeded_db):
r = s.get(f"{BASE_URL}/admin/containers/projects/search?q=")
assert r.status_code == 200
assert r.json()["results"] == []
def test_project_search_hides_other_admin_private_project(app_server):
_, _, owner_key = _make_admin_search()
_, _, other_admin_key = _make_admin_search()
title = f"SearchHidden{int(time.time() * 1000)}"
slug = _create_private_project(owner_key, title)
other = requests.Session()
other.headers.update({"X-API-KEY": other_admin_key})
r = other.get(f"{BASE_URL}/admin/containers/projects/search?q={title}")
assert r.status_code == 200
assert slug not in [row["slug"] for row in r.json()["results"]]
owner = requests.Session()
owner.headers.update({"X-API-KEY": owner_key})
r = owner.get(f"{BASE_URL}/admin/containers/projects/search?q={title}")
assert slug in [row["slug"] for row in r.json()["results"]]