Gate operator reports behind their own grant
Some checks failed
CI / backend (push) Has been cancelled
CI / naming (push) Has been cancelled
CI / gates (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / lean-build (push) Has been cancelled

Operator report screenshots are an uncontrolled capture of whatever was
on the operator's screen when they filed the report, so they can carry
part numbers, customer names, or other shopfloor content. The rest of
endpoints.view is uniform machine-collected telemetry. Reading a PC's
CPU load does not imply a right to read someone's screen, so the reports
and their images move to endpoints.operatorreports.

The image route is the real control point, not the panel: the panel only
decides whether a tab is drawn, while the bytes stay reachable by URL to
anyone who can guess an id. Both GET routes now require the new grant.

Migration 7d47 backfills the grant to everyone holding endpoints.view so
nobody loses a panel they were already using; tightening from there is a
per-account decision in the user editor. The grant is in the scope
denylist, so it is a per-user direct grant and Reporter cannot hold it.

Also adds test_user_editor_checkboxes_match_the_server_action_list. The
SCOPE_ENDPOINT_ACTIONS comment claimed a sync guard kept the Vue
checkbox list aligned with the Python tuple, but no such test existed.
The lists had already drifted: endpoints.operatorreports reached a
running container with no way to grant it through the UI. The guard
catches drift in both directions - a name only in Python ships the
feature dark, and a name only in Vue is worse, since the admin ticks a
box and believes access was granted while the server refuses to store it.
This commit is contained in:
benjamin.baker
2026-09-08 07:36:19 -04:00
parent 2cf46da55e
commit 68ddf87d3b
8 changed files with 280 additions and 10 deletions

View File

@@ -18,6 +18,37 @@ def _core_catalog():
return list(Permission.CORE_PERMISSIONS)
def test_user_editor_checkboxes_match_the_server_action_list():
"""The Vue checkbox list must match SCOPE_ENDPOINT_ACTIONS exactly.
These two lists are maintained by hand in different languages, so they drift
silently and in both directions. A name in Python but not in Vue is a grant
no admin can actually give - the feature ships dark. A name in Vue but not in
Python is worse: the checkbox appears, the admin ticks it believing access
was granted, and the server refuses to store it.
This is the guard the SCOPE_ENDPOINT_ACTIONS comment claims exists. It did
not, which is exactly how endpoints.operatorreports reached a running
container with no way to grant it.
"""
import re
from pathlib import Path
source = Path(__file__).resolve().parents[2].joinpath(
'frontend', 'src', 'views', 'settings', 'UsersList.vue').read_text(
encoding='utf-8')
block = re.search(
r'const endpointActionPermissions = \[(.*?)\]', source, re.S)
assert block, 'endpointActionPermissions not found in UsersList.vue'
infrontend = set(re.findall(r"name:\s*'([^']+)'", block.group(1)))
assert infrontend == set(SCOPE_ENDPOINT_ACTIONS), (
'User editor checkboxes and SCOPE_ENDPOINT_ACTIONS disagree. '
f'Only in Vue: {sorted(infrontend - set(SCOPE_ENDPOINT_ACTIONS))}. '
f'Only in Python: {sorted(set(SCOPE_ENDPOINT_ACTIONS) - infrontend)}.')
# ---------------------------------------------------------------------------
# Derived grant lists
# ---------------------------------------------------------------------------