mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 03:54:17 +02:00
fix(backup): require settings:update to restore the settings category (#2656)
The restore endpoint was gated on `github:restore` alone, and the settings category rewrites arbitrary non-auth `Settings` rows. Backup and Settings are separate permission groups, so a role holding only Backup could change settings it cannot change through `PUT /api/v1/settings/`, the endpoint that owns them. The inconsistency is ours rather than an inference: this module already makes exactly this argument — it is why the four protected auth keys are refused outright — and `library.py` sets the precedent of elevating a route to `settings:update` for the same reason. Gated per-category rather than by demoting `github:restore` wholesale, so it stays narrow and doesn't presume the answer for `spools:*`, `archives:*` and `kprofiles`. That broader permission-model question goes to the maintainer in the PR reply. `current_user is None` only means auth is disabled — `github:restore` is in `_APIKEY_DENIED_PERMISSIONS`, so an API key never reaches the route body. No frontend change: `request()` puts the 403's `detail` on the Error, and the modal already renders `restoreMutation.error.message` in its red block, so the user sees the missing permission named. Tests: 1 regression (a Backup-only role gets 403 and `run_restore` is never awaited) + 3 controls (the same role still restores the other three categories; a role holding both permissions still restores settings; auth disabled is unaffected). The regression confirmed failing against the pre-fix route. `_create_config` gained an optional token so it works under auth.
This commit is contained in:
@@ -26,6 +26,7 @@ from backend.app.schemas.github_backup import (
|
||||
GitHubRestoreResponse,
|
||||
GitHubTestConnectionResponse,
|
||||
ProviderType,
|
||||
RestoreCategory,
|
||||
)
|
||||
from backend.app.services.github_backup import github_backup_service
|
||||
from backend.app.services.github_restore import github_restore_service
|
||||
@@ -440,14 +441,35 @@ async def preview_restore(
|
||||
async def restore_backup(
|
||||
request: GitHubRestoreRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: User | None = RequirePermissionIfAuthEnabled(Permission.GITHUB_RESTORE),
|
||||
current_user: User | None = RequirePermissionIfAuthEnabled(Permission.GITHUB_RESTORE),
|
||||
):
|
||||
"""Restore selected categories from one backup commit.
|
||||
|
||||
Note there is no private-repo gate here, unlike the config endpoints: that
|
||||
check exists to stop credentials leaving the instance, and this path only
|
||||
reads. A config can only be saved against a private repo anyway.
|
||||
|
||||
The settings category needs ``settings:update`` as well — see the check
|
||||
below.
|
||||
"""
|
||||
if RestoreCategory.SETTINGS in request.categories and current_user is not None:
|
||||
# The settings category rewrites arbitrary non-auth Settings rows, which
|
||||
# is what PUT /api/v1/settings/ gates on settings:update. Backup and
|
||||
# Settings are separate permission groups, so a role holding only Backup
|
||||
# could otherwise change settings it cannot change through the endpoint
|
||||
# that owns them. This module already makes that argument — it is why
|
||||
# the four protected auth keys are refused outright — so the gap was an
|
||||
# inconsistency in ours, not a new policy.
|
||||
#
|
||||
# current_user is None only when auth is disabled: github:restore is in
|
||||
# _APIKEY_DENIED_PERMISSIONS, so an API key never gets past the
|
||||
# dependency to reach this line.
|
||||
if not current_user.has_all_permissions(Permission.SETTINGS_UPDATE.value):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=f"Missing required permissions: {Permission.SETTINGS_UPDATE.value}",
|
||||
)
|
||||
|
||||
result = await db.execute(select(GitHubBackupConfig).limit(1))
|
||||
config = result.scalar_one_or_none()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user