Admin: allow changing your own quota
The self-protection guard refused every change to the admin's own account. It now only refuses blocking and deleting yourself; a quota change is fine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,9 @@
|
||||
The backend seam for the admin panel: the registration gate — the first account
|
||||
on a fresh install becomes the administrator and closes signup behind itself,
|
||||
and this is how that gets reopened — the default quota, and managing users
|
||||
(search, per-user quota, block, delete). An administrator cannot block or delete
|
||||
their own account, so the instance can never lock out its last admin that way.
|
||||
(search, per-user quota, block, delete). An administrator can change their own
|
||||
quota but cannot block or delete their own account, so the instance can never
|
||||
lock out its last admin that way.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -280,12 +281,15 @@ def _user_out(user: User, used_bytes: int, accounts: list[str]) -> AdminUserOut:
|
||||
)
|
||||
|
||||
|
||||
async def _target(db: AsyncSession, admin: User, user_id: uuid.UUID) -> User:
|
||||
"""The user an admin action is aimed at; never the admin themselves."""
|
||||
async def _target(
|
||||
db: AsyncSession, admin: User, user_id: uuid.UUID, *, allow_self: bool = False
|
||||
) -> User:
|
||||
"""The user an admin action is aimed at. Blocking and deleting are never
|
||||
allowed on the admin themselves; a quota change is (``allow_self``)."""
|
||||
user = await db.scalar(select(User).where(User.id == user_id))
|
||||
if user is None:
|
||||
raise_api_error(404, "not_found", "user not found")
|
||||
if user.id == admin.id:
|
||||
if user.id == admin.id and not allow_self:
|
||||
raise_api_error(409, "cannot_modify_self", "you cannot do this to your own account")
|
||||
return user
|
||||
|
||||
@@ -303,7 +307,8 @@ async def patch_user(
|
||||
user_id: uuid.UUID, body: AdminUserPatch, admin: CurrentAdmin, db: DbSession
|
||||
) -> AdminUserOut:
|
||||
"""Change a user's quota and/or block or unblock them."""
|
||||
user = await _target(db, admin, user_id)
|
||||
# Your own quota is fine to change; blocking yourself is not.
|
||||
user = await _target(db, admin, user_id, allow_self=body.blocked is None)
|
||||
if body.quota_bytes is not None:
|
||||
user.quota_bytes = body.quota_bytes
|
||||
if body.blocked is not None:
|
||||
|
||||
@@ -168,6 +168,20 @@ async def test_admin_cannot_block_or_delete_themselves(world: World) -> None:
|
||||
assert (await c.delete(f"/api/admin/users/{admin.id}")).status_code == 409
|
||||
|
||||
|
||||
async def test_admin_can_change_their_own_quota(world: World) -> None:
|
||||
admin = await world.user(admin=True)
|
||||
async with await world.client(admin) as c:
|
||||
resp = await c.patch(f"/api/admin/users/{admin.id}", json={"quota_bytes": 5 * 2**30})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["quota_bytes"] == 5 * 2**30
|
||||
# Quota together with a block is still refused: the block is the problem.
|
||||
assert (
|
||||
await c.patch(
|
||||
f"/api/admin/users/{admin.id}", json={"quota_bytes": 2**30, "blocked": True}
|
||||
)
|
||||
).status_code == 409
|
||||
|
||||
|
||||
async def test_delete_removes_the_user_and_their_data(world: World, bus: _Bus) -> None:
|
||||
admin = await world.user(admin=True)
|
||||
target = await world.user()
|
||||
|
||||
Reference in New Issue
Block a user