fix(permissions): self-heal Administrators to ALL_PERMISSIONS on upgrade + Pipelines runs dashboard polish

Administrators system group sync
- Fresh installs already bootstrap with ALL_PERMISSIONS, so they always have
  every permission. Upgrades previously only got what one-off backfill blocks
  in seed_default_groups() explicitly listed (library:purge, archives:purge,
  the OWN/ALL read-flag block, orca_cloud:auth, pipelines:*). Any Permission
  enum member added without a matching block silently stayed missing on
  existing admin rows. The most recent gap was printer_sensor_history:read
  (Sensor History charts returned 403 for upgraded admins).
- seed_default_groups() now syncs Administrators to ALL_PERMISSIONS on every
  startup: append every Permission value that isn't already on the row.
  Additive only -- hand-added custom permissions are preserved.
- The pure-admin one-off backfills (library:purge / archives:purge block,
  the OWN/ALL + orca_cloud:auth + legacy-read-flag block, the Administrators
  branch of the pipeline backfill) are retired since the sync subsumes
  them. Non-admin backfills (Operators / Viewers OWN-tier reads, Operators
  orca_cloud:auth, pipelines for non-admin groups, makerworld:*, clear_plate
  cross-group adders) are untouched.
- Tests: test_administrators_printer_sensor_history_read_backfilled
  (regression for the reported gap),
  test_administrators_sync_covers_every_current_permission (generic
  invariant -- any future new permission lands on admin without needing
  a one-off test), test_administrators_sync_is_additive_only (custom
  permissions preserved). 12/12 backfill-migration + 102/102 broader
  permission tests green; ruff clean.

Pipelines runs dashboard
- PipelineRunsPage.tsx: the Pipeline / Status / Target filter row's three
  native <select> elements are replaced with a bambu-themed FilterDropdown
  (button trigger, floating menu, optgroup-style headers for the Target
  picker, hover + selected states with a check mark, closes on outside
  click and Escape). Same value/onChange contract -- visual only.
- SlicerPipelinesPanel.tsx: wrap list?.pipelines ?? [] in useMemo so the
  reference is stable when the data is stable. Fixes the
  react-hooks/exhaustive-deps warning where the inline fallback returned
  a fresh empty array every render, invalidating both downstream useMemo
  caches (target-options + filtered-pipelines list).
This commit is contained in:
maziggy
2026-06-28 11:18:18 +02:00
parent b5263eb5cd
commit b23cb69a66
29 changed files with 1619 additions and 392 deletions
+23 -62
View File
@@ -3345,7 +3345,7 @@ async def seed_default_groups():
from sqlalchemy import select
from backend.app.core.permissions import DEFAULT_GROUPS
from backend.app.core.permissions import ALL_PERMISSIONS, DEFAULT_GROUPS
from backend.app.models.group import Group
from backend.app.models.user import User
@@ -3498,63 +3498,31 @@ async def seed_default_groups():
group.permissions = perms
await session.commit()
# Backfill library:purge + archives:purge for the Administrators group
# on existing installs. Both permissions were added after Administrators
# was first seeded, so upgrading users miss them even though the default
# config (ALL_PERMISSIONS) includes them for fresh installs.
# Backfill: sync the Administrators system group to ALL_PERMISSIONS.
# Administrators' contract is full access to every feature — fresh
# installs get that via DEFAULT_GROUPS["Administrators"]["permissions"]
# = ALL_PERMISSIONS. Upgrading installs would otherwise stay frozen at
# whatever permission set existed when they were first seeded, so a
# newly-added Permission enum member silently leaves admins gated out
# of the feature it controls.
#
# Generalises the previous one-off admin backfills (library:purge,
# archives:purge, the OWN/ALL read-flag set + legacy read flags,
# orca_cloud:auth, printer_sensor_history:read, …): every current
# Permission enum value is appended to the admin group if missing.
# Additive only — never removes a permission an operator added by
# hand. Run AFTER the legacy-rename migration above so the renamed
# OWN/ALL variants land in the group before the sync sees them.
result = await session.execute(select(Group).where(Group.name == "Administrators"))
admin_group = result.scalar_one_or_none()
if admin_group and admin_group.permissions is not None:
perms = list(admin_group.permissions)
added = False
for new_perm in ("library:purge", "archives:purge"):
for new_perm in ALL_PERMISSIONS:
if new_perm not in perms:
perms.append(new_perm)
added = True
logger.info("Added %s to Administrators group (backfill)", new_perm)
if added:
admin_group.permissions = perms
await session.commit()
# Backfill the read flag set for the Administrators group on existing
# installs (maziggy/bambuddy-security #2). Two layers:
#
# (a) New OWN/ALL splits — `archives:read_own` etc. Fresh installs get
# these via ALL_PERMISSIONS; upgrades need the explicit backfill
# so admin's permission set matches a fresh install's.
#
# (b) Legacy `archives:read` / `library:read` / `queue:read`. The
# frontend still gates download / preview UI on these LEGACY
# strings (see ArchivesPage / FileManagerPage), so admin needs
# them retained even though the new API uses the OWN/ALL split.
# The PERMISSION_MIGRATION_ALL map deliberately doesn't rename
# read flags for admin — this backfill ensures they're present
# even if they were stripped by hand or by an older migration.
#
# Also includes orca_cloud:auth for parity with fresh-install
# behaviour (ALL_PERMISSIONS covers it; backfill makes sure an
# admin role that's been customised since seed still has it).
result = await session.execute(select(Group).where(Group.name == "Administrators"))
admin_group = result.scalar_one_or_none()
if admin_group and admin_group.permissions is not None:
perms = list(admin_group.permissions)
added = False
for new_perm in (
"archives:read",
"archives:read_own",
"archives:read_all",
"library:read",
"library:read_own",
"library:read_all",
"queue:read",
"queue:read_own",
"queue:read_all",
"orca_cloud:auth",
):
if new_perm not in perms:
perms.append(new_perm)
added = True
logger.info("Added %s to Administrators group (backfill)", new_perm)
logger.info("Added %s to Administrators group (ALL_PERMISSIONS sync)", new_perm)
if added:
admin_group.permissions = perms
await session.commit()
@@ -3613,25 +3581,18 @@ async def seed_default_groups():
group.permissions = perms
await session.commit()
# Backfill pipeline permissions (#1425). Pipelines were added after
# initial seeding, so existing groups need them appended:
# - Administrators: all three (matches fresh-install ALL_PERMISSIONS)
# Backfill pipeline permissions (#1425) for non-admin groups.
# Administrators is handled by the ALL_PERMISSIONS sync above.
# - Operators: all three (matches fresh-install DEFAULT_GROUPS)
# - Viewers + any group with library:read_own or settings:read:
# - Any other group with library:read_own or settings:read:
# pipelines:read only
result = await session.execute(select(Group))
for group in result.scalars().all():
if not group.permissions:
if not group.permissions or group.name == "Administrators":
continue
perms = list(group.permissions)
changed = False
if group.name == "Administrators":
for new_perm in ("pipelines:read", "pipelines:write", "pipelines:run"):
if new_perm not in perms:
perms.append(new_perm)
changed = True
logger.info("Added %s to Administrators group (backfill)", new_perm)
elif group.name == "Operators":
if group.name == "Operators":
for new_perm in ("pipelines:read", "pipelines:write", "pipelines:run"):
if new_perm not in perms:
perms.append(new_perm)