Major refactoring of admin API (permissions, paths)

This commit is contained in:
Leo Vasanko 2025-09-02 18:08:06 -06:00
parent bfc777fb56
commit c9f9b28bf4
2 changed files with 87 additions and 142 deletions

View File

@ -147,7 +147,7 @@ async function loadOrgs() {
if (data.detail) throw new Error(data.detail) if (data.detail) throw new Error(data.detail)
// Restructure to attach users to roles instead of flat user list at org level // Restructure to attach users to roles instead of flat user list at org level
orgs.value = data.map(o => { orgs.value = data.map(o => {
const roles = o.roles.map(r => ({ ...r, users: [] })) const roles = o.roles.map(r => ({ ...r, org_uuid: o.uuid, users: [] }))
const roleMap = Object.fromEntries(roles.map(r => [r.display_name, r])) const roleMap = Object.fromEntries(roles.map(r => [r.display_name, r]))
for (const u of o.users || []) { for (const u of o.users || []) {
if (roleMap[u.role]) roleMap[u.role].users.push(u) if (roleMap[u.role]) roleMap[u.role].users.push(u)
@ -250,7 +250,7 @@ function updateRole(role) { openDialog('role-update', { role }) }
function deleteRole(role) { function deleteRole(role) {
openDialog('confirm', { message: `Delete role ${role.display_name}?`, action: async () => { openDialog('confirm', { message: `Delete role ${role.display_name}?`, action: async () => {
const res = await fetch(`/auth/admin/roles/${role.uuid}`, { method: 'DELETE' }) const res = await fetch(`/auth/admin/orgs/${role.org_uuid}/roles/${role.uuid}`, { method: 'DELETE' })
const data = await res.json(); if (data.detail) throw new Error(data.detail) const data = await res.json(); if (data.detail) throw new Error(data.detail)
await loadOrgs() await loadOrgs()
} }) } })
@ -321,7 +321,7 @@ const pageHeading = computed(() => {
watch(selectedUser, async (u) => { watch(selectedUser, async (u) => {
if (!u) { userDetail.value = null; return } if (!u) { userDetail.value = null; return }
try { try {
const res = await fetch(`/auth/admin/users/${u.uuid}`) const res = await fetch(`/auth/admin/orgs/${u.org_uuid}/users/${u.uuid}`)
const data = await res.json() const data = await res.json()
if (data.detail) throw new Error(data.detail) if (data.detail) throw new Error(data.detail)
userDetail.value = data userDetail.value = data
@ -359,7 +359,7 @@ async function toggleRolePermission(role, permId, checked) {
const prev = [...role.permissions] const prev = [...role.permissions]
role.permissions = next role.permissions = next
try { try {
const res = await fetch(`/auth/admin/roles/${role.uuid}`, { const res = await fetch(`/auth/admin/orgs/${role.org_uuid}/roles/${role.uuid}`, {
method: 'PUT', method: 'PUT',
headers: { 'content-type': 'application/json' }, headers: { 'content-type': 'application/json' },
body: JSON.stringify({ display_name: role.display_name, permissions: next }) body: JSON.stringify({ display_name: role.display_name, permissions: next })
@ -379,7 +379,7 @@ async function onUserNameSaved() {
await loadOrgs() await loadOrgs()
if (selectedUser.value) { if (selectedUser.value) {
try { try {
const r = await fetch(`/auth/admin/users/${selectedUser.value.uuid}`) const r = await fetch(`/auth/admin/orgs/${selectedUser.value.org_uuid}/users/${selectedUser.value.uuid}`)
const jd = await r.json() const jd = await r.json()
if (!r.ok || jd.detail) throw new Error(jd.detail || 'Reload failed') if (!r.ok || jd.detail) throw new Error(jd.detail || 'Reload failed')
userDetail.value = jd userDetail.value = jd
@ -409,7 +409,7 @@ async function submitDialog() {
const { role } = dialog.value.data; const name = dialog.value.data.name?.trim(); if (!name) throw new Error('Name required') const { role } = dialog.value.data; const name = dialog.value.data.name?.trim(); if (!name) throw new Error('Name required')
const permsCsv = dialog.value.data.perms || '' const permsCsv = dialog.value.data.perms || ''
const perms = permsCsv.split(',').map(s=>s.trim()).filter(Boolean) const perms = permsCsv.split(',').map(s=>s.trim()).filter(Boolean)
const res = await fetch(`/auth/admin/roles/${role.uuid}`, { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ display_name: name, permissions: perms }) }) const res = await fetch(`/auth/admin/orgs/${role.org_uuid}/roles/${role.uuid}`, { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ display_name: name, permissions: perms }) })
const d = await res.json(); if (d.detail) throw new Error(d.detail); await loadOrgs() const d = await res.json(); if (d.detail) throw new Error(d.detail); await loadOrgs()
} else if (t === 'user-create') { } else if (t === 'user-create') {
const { org, role } = dialog.value.data; const name = dialog.value.data.name?.trim(); if (!name) throw new Error('Name required') const { org, role } = dialog.value.data; const name = dialog.value.data.name?.trim(); if (!name) throw new Error('Name required')
@ -496,7 +496,7 @@ async function submitDialog() {
:loading="loading" :loading="loading"
:org-display-name="userDetail.org.display_name" :org-display-name="userDetail.org.display_name"
:role-name="userDetail.role" :role-name="userDetail.role"
:update-endpoint="`/auth/admin/users/${selectedUser.uuid}/display-name`" :update-endpoint="`/auth/admin/orgs/${selectedUser.org_uuid}/users/${selectedUser.uuid}/display-name`"
@saved="onUserNameSaved" @saved="onUserNameSaved"
/> />
<div v-else-if="userDetail?.error" class="error small">{{ userDetail.error }}</div> <div v-else-if="userDetail?.error" class="error small">{{ userDetail.error }}</div>
@ -512,7 +512,7 @@ async function submitDialog() {
<p class="matrix-hint muted">Use the token dialog to register a new credential for the member.</p> <p class="matrix-hint muted">Use the token dialog to register a new credential for the member.</p>
<RegistrationLinkModal <RegistrationLinkModal
v-if="showRegModal" v-if="showRegModal"
:endpoint="`/auth/admin/users/${selectedUser.uuid}/create-link`" :endpoint="`/auth/admin/orgs/${selectedUser.org_uuid}/users/${selectedUser.uuid}/create-link`"
:auto-copy="false" :auto-copy="false"
@close="showRegModal = false" @close="showRegModal = false"
@copied="onLinkCopied" @copied="onLinkCopied"

View File

@ -8,6 +8,7 @@ from ..authsession import expires
from ..globals import db from ..globals import db
from ..globals import passkey as global_passkey from ..globals import passkey as global_passkey
from ..util import frontend, passphrase, permutil, querysafe, tokens from ..util import frontend, passphrase, permutil, querysafe, tokens
from . import authz
app = FastAPI() app = FastAPI()
@ -25,10 +26,11 @@ async def general_exception_handler(_request, exc: Exception):
@app.get("/") @app.get("/")
async def admin_frontend(auth=Cookie(None)): async def admin_frontend(auth=Cookie(None)):
ctx = await permutil.session_context(auth) try:
if permutil.has_any(ctx, ["auth:admin", "auth:org:*"]): await authz.verify(auth, ["auth:admin", "auth:org:*"], match=permutil.has_any)
return FileResponse(frontend.file("admin/index.html")) return FileResponse(frontend.file("admin/index.html"))
return FileResponse(frontend.file("index.html"), status_code=401 if ctx else 403) except HTTPException as e:
return FileResponse(frontend.file("index.html"), status_code=e.status_code)
# -------------------- Organizations -------------------- # -------------------- Organizations --------------------
@ -36,12 +38,10 @@ async def admin_frontend(auth=Cookie(None)):
@app.get("/orgs") @app.get("/orgs")
async def admin_list_orgs(auth=Cookie(None)): async def admin_list_orgs(auth=Cookie(None)):
ctx = await permutil.session_context(auth) ctx = await authz.verify(auth, ["auth:admin", "auth:org:*"], match=permutil.has_any)
if not permutil.has_any(ctx, ["auth:admin", "auth:org:*"]):
raise ValueError("Insufficient permissions")
orgs = await db.instance.list_organizations() orgs = await db.instance.list_organizations()
if not permutil.has_any(ctx, ["auth:admin"]): # limit org admin to their own org if "auth:admin" not in ctx.role.permissions:
orgs = [o for o in orgs if o.uuid == ctx.org.uuid] orgs = [o for o in orgs if f"auth:org:{o.uuid}" in ctx.role.permissions]
def role_to_dict(r): def role_to_dict(r):
return { return {
@ -75,9 +75,7 @@ async def admin_list_orgs(auth=Cookie(None)):
@app.post("/orgs") @app.post("/orgs")
async def admin_create_org(payload: dict = Body(...), auth=Cookie(None)): async def admin_create_org(payload: dict = Body(...), auth=Cookie(None)):
ctx = await permutil.session_context(auth) await authz.verify(auth, ["auth:admin"])
if not permutil.has_any(ctx, ["auth:admin"]):
raise ValueError("Global admin required")
from ..db import Org as OrgDC # local import to avoid cycles from ..db import Org as OrgDC # local import to avoid cycles
org_uuid = uuid4() org_uuid = uuid4()
@ -92,9 +90,9 @@ async def admin_create_org(payload: dict = Body(...), auth=Cookie(None)):
async def admin_update_org( async def admin_update_org(
org_uuid: UUID, payload: dict = Body(...), auth=Cookie(None) org_uuid: UUID, payload: dict = Body(...), auth=Cookie(None)
): ):
ctx = await permutil.session_context(auth) await authz.verify(
if not permutil.has_any(ctx, ["auth:admin"]): auth, ["auth:admin", f"auth:org:{org_uuid}"], match=permutil.has_any
raise ValueError("Global admin required") )
from ..db import Org as OrgDC # local import to avoid cycles from ..db import Org as OrgDC # local import to avoid cycles
current = await db.instance.get_organization(str(org_uuid)) current = await db.instance.get_organization(str(org_uuid))
@ -107,14 +105,10 @@ async def admin_update_org(
@app.delete("/orgs/{org_uuid}") @app.delete("/orgs/{org_uuid}")
async def admin_delete_org(org_uuid: UUID, auth=Cookie(None)): async def admin_delete_org(org_uuid: UUID, auth=Cookie(None)):
ctx = await permutil.session_context(auth) ctx = await authz.verify(
if not permutil.has_any(ctx, ["auth:admin"]): auth, ["auth:admin", f"auth:org:{org_uuid}"], match=permutil.has_any
raise ValueError("Global admin required") )
try: if ctx.org.uuid == org_uuid:
acting_org_uuid = ctx.org.uuid if ctx.org else None
except Exception: # pragma: no cover - defensive
acting_org_uuid = None
if acting_org_uuid and acting_org_uuid == org_uuid:
raise ValueError("Cannot delete the organization you belong to") raise ValueError("Cannot delete the organization you belong to")
await db.instance.delete_organization(org_uuid) await db.instance.delete_organization(org_uuid)
return {"status": "ok"} return {"status": "ok"}
@ -124,15 +118,7 @@ async def admin_delete_org(org_uuid: UUID, auth=Cookie(None)):
async def admin_add_org_permission( async def admin_add_org_permission(
org_uuid: UUID, permission_id: str, auth=Cookie(None) org_uuid: UUID, permission_id: str, auth=Cookie(None)
): ):
ctx = await permutil.session_context(auth) await authz.verify(auth, ["auth:admin"])
if not (
permutil.has_any(ctx, ["auth:admin"])
or (
permutil.has_any(ctx, [f"auth:org:{org_uuid}"]) and ctx.org.uuid == org_uuid
)
):
raise ValueError("Insufficient permissions")
querysafe.assert_safe(permission_id, field="permission_id")
await db.instance.add_permission_to_organization(str(org_uuid), permission_id) await db.instance.add_permission_to_organization(str(org_uuid), permission_id)
return {"status": "ok"} return {"status": "ok"}
@ -141,16 +127,7 @@ async def admin_add_org_permission(
async def admin_remove_org_permission( async def admin_remove_org_permission(
org_uuid: UUID, permission_id: str, auth=Cookie(None) org_uuid: UUID, permission_id: str, auth=Cookie(None)
): ):
ctx = await permutil.session_context(auth) await authz.verify(auth, ["auth:admin"])
if not ctx or not (
permutil.has_any(ctx, ["auth:admin"])
or (
permutil.has_any(ctx, [f"auth:org:{org_uuid}"])
and getattr(ctx.org, "uuid", None) == org_uuid
)
):
raise ValueError("Insufficient permissions")
querysafe.assert_safe(permission_id, field="permission_id")
await db.instance.remove_permission_from_organization(str(org_uuid), permission_id) await db.instance.remove_permission_from_organization(str(org_uuid), permission_id)
return {"status": "ok"} return {"status": "ok"}
@ -162,15 +139,7 @@ async def admin_remove_org_permission(
async def admin_create_role( async def admin_create_role(
org_uuid: UUID, payload: dict = Body(...), auth=Cookie(None) org_uuid: UUID, payload: dict = Body(...), auth=Cookie(None)
): ):
ctx = await permutil.session_context(auth) await authz.verify(auth, ["auth:admin", f"auth:org:{org_uuid}"])
if not ctx or not (
permutil.has_any(ctx, ["auth:admin"])
or (
permutil.has_any(ctx, [f"auth:org:{org_uuid}"])
and getattr(ctx.org, "uuid", None) == org_uuid
)
):
raise ValueError("Insufficient permissions")
from ..db import Role as RoleDC from ..db import Role as RoleDC
role_uuid = uuid4() role_uuid = uuid4()
@ -192,25 +161,22 @@ async def admin_create_role(
return {"uuid": str(role_uuid)} return {"uuid": str(role_uuid)}
@app.put("/roles/{role_uuid}") @app.put("/orgs/{org_uuid}/roles/{role_uuid}")
async def admin_update_role( async def admin_update_role(
role_uuid: UUID, payload: dict = Body(...), auth=Cookie(None) org_uuid: UUID, role_uuid: UUID, payload: dict = Body(...), auth=Cookie(None)
): ):
ctx = await permutil.session_context(auth) # Verify caller is global admin or admin of provided org
await authz.verify(
auth, ["auth:admin", f"auth:org:{org_uuid}"], match=permutil.has_any
)
role = await db.instance.get_role(role_uuid) role = await db.instance.get_role(role_uuid)
if not ctx or not ( if role.org_uuid != org_uuid:
permutil.has_any(ctx, ["auth:admin"]) raise HTTPException(status_code=404, detail="Role not found in organization")
or (
permutil.has_any(ctx, [f"auth:org:{role.org_uuid}"])
and getattr(ctx.org, "uuid", None) == role.org_uuid
)
):
raise ValueError("Insufficient permissions")
from ..db import Role as RoleDC from ..db import Role as RoleDC
display_name = payload.get("display_name") or role.display_name display_name = payload.get("display_name") or role.display_name
permissions = payload.get("permissions") or role.permissions permissions = payload.get("permissions") or role.permissions
org = await db.instance.get_organization(str(role.org_uuid)) org = await db.instance.get_organization(str(org_uuid))
grantable = set(org.permissions or []) grantable = set(org.permissions or [])
for pid in permissions: for pid in permissions:
await db.instance.get_permission(pid) await db.instance.get_permission(pid)
@ -218,7 +184,7 @@ async def admin_update_role(
raise ValueError(f"Permission not grantable by org: {pid}") raise ValueError(f"Permission not grantable by org: {pid}")
updated = RoleDC( updated = RoleDC(
uuid=role_uuid, uuid=role_uuid,
org_uuid=role.org_uuid, org_uuid=org_uuid,
display_name=display_name, display_name=display_name,
permissions=permissions, permissions=permissions,
) )
@ -226,18 +192,14 @@ async def admin_update_role(
return {"status": "ok"} return {"status": "ok"}
@app.delete("/roles/{role_uuid}") @app.delete("/orgs/{org_uuid}/roles/{role_uuid}")
async def admin_delete_role(role_uuid: UUID, auth=Cookie(None)): async def admin_delete_role(org_uuid: UUID, role_uuid: UUID, auth=Cookie(None)):
ctx = await permutil.session_context(auth) await authz.verify(
auth, ["auth:admin", f"auth:org:{org_uuid}"], match=permutil.has_any
)
role = await db.instance.get_role(role_uuid) role = await db.instance.get_role(role_uuid)
if not ctx or not ( if role.org_uuid != org_uuid:
permutil.has_any(ctx, ["auth:admin"]) raise HTTPException(status_code=404, detail="Role not found in organization")
or (
permutil.has_any(ctx, [f"auth:org:{role.org_uuid}"])
and getattr(ctx.org, "uuid", None) == role.org_uuid
)
):
raise ValueError("Insufficient permissions")
await db.instance.delete_role(role_uuid) await db.instance.delete_role(role_uuid)
return {"status": "ok"} return {"status": "ok"}
@ -249,15 +211,9 @@ async def admin_delete_role(role_uuid: UUID, auth=Cookie(None)):
async def admin_create_user( async def admin_create_user(
org_uuid: UUID, payload: dict = Body(...), auth=Cookie(None) org_uuid: UUID, payload: dict = Body(...), auth=Cookie(None)
): ):
ctx = await permutil.session_context(auth) await authz.verify(
if not ctx or not ( auth, ["auth:admin", f"auth:org:{org_uuid}"], match=permutil.has_any
permutil.has_any(ctx, ["auth:admin"]) )
or (
permutil.has_any(ctx, [f"auth:org:{org_uuid}"])
and getattr(ctx.org, "uuid", None) == org_uuid
)
):
raise ValueError("Insufficient permissions")
display_name = payload.get("display_name") display_name = payload.get("display_name")
role_name = payload.get("role") role_name = payload.get("role")
if not display_name or not role_name: if not display_name or not role_name:
@ -284,15 +240,9 @@ async def admin_create_user(
async def admin_update_user_role( async def admin_update_user_role(
org_uuid: UUID, user_uuid: UUID, payload: dict = Body(...), auth=Cookie(None) org_uuid: UUID, user_uuid: UUID, payload: dict = Body(...), auth=Cookie(None)
): ):
ctx = await permutil.session_context(auth) await authz.verify(
if not ctx or not ( auth, ["auth:admin", f"auth:org:{org_uuid}"], match=permutil.has_any
permutil.has_any(ctx, ["auth:admin"]) )
or (
permutil.has_any(ctx, [f"auth:org:{org_uuid}"])
and getattr(ctx.org, "uuid", None) == org_uuid
)
):
raise ValueError("Insufficient permissions")
new_role = payload.get("role") new_role = payload.get("role")
if not new_role: if not new_role:
raise ValueError("role is required") raise ValueError("role is required")
@ -309,19 +259,22 @@ async def admin_update_user_role(
return {"status": "ok"} return {"status": "ok"}
@app.post("/users/{user_uuid}/create-link") @app.post("/orgs/{org_uuid}/users/{user_uuid}/create-link")
async def admin_create_user_registration_link(user_uuid: UUID, auth=Cookie(None)): async def admin_create_user_registration_link(
ctx = await permutil.session_context(auth) org_uuid: UUID, user_uuid: UUID, auth=Cookie(None)
):
try: try:
user_org, _role_name = await db.instance.get_user_organization(user_uuid) user_org, _role_name = await db.instance.get_user_organization(user_uuid)
except ValueError: except ValueError:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
if not ctx or not ( if user_org.uuid != org_uuid:
permutil.has_any(ctx, ["auth:admin"]) raise HTTPException(status_code=404, detail="User not found in organization")
or ( ctx = await authz.verify(
permutil.has_any(ctx, [f"auth:org:{user_org.uuid}"]) auth, ["auth:admin", f"auth:org:{org_uuid}"], match=permutil.has_any
and getattr(ctx.org, "uuid", None) == user_org.uuid )
) if (
"auth:admin" not in ctx.role.permissions
and f"auth:org:{org_uuid}" not in ctx.role.permissions
): ):
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
token = passphrase.generate() token = passphrase.generate()
@ -336,19 +289,20 @@ async def admin_create_user_registration_link(user_uuid: UUID, auth=Cookie(None)
return {"url": url, "expires": expires().isoformat()} return {"url": url, "expires": expires().isoformat()}
@app.get("/users/{user_uuid}") @app.get("/orgs/{org_uuid}/users/{user_uuid}")
async def admin_get_user_detail(user_uuid: UUID, auth=Cookie(None)): async def admin_get_user_detail(org_uuid: UUID, user_uuid: UUID, auth=Cookie(None)):
ctx = await permutil.session_context(auth)
try: try:
user_org, role_name = await db.instance.get_user_organization(user_uuid) user_org, role_name = await db.instance.get_user_organization(user_uuid)
except ValueError: except ValueError:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
if not ctx or not ( if user_org.uuid != org_uuid:
permutil.has_any(ctx, ["auth:admin"]) raise HTTPException(status_code=404, detail="User not found in organization")
or ( ctx = await authz.verify(
permutil.has_any(ctx, [f"auth:org:{user_org.uuid}"]) auth, ["auth:admin", f"auth:org:{org_uuid}"], match=permutil.has_any
and getattr(ctx.org, "uuid", None) == user_org.uuid )
) if (
"auth:admin" not in ctx.role.permissions
and f"auth:org:{org_uuid}" not in ctx.role.permissions
): ):
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
user = await db.instance.get_user_by_uuid(user_uuid) user = await db.instance.get_user_by_uuid(user_uuid)
@ -389,21 +343,22 @@ async def admin_get_user_detail(user_uuid: UUID, auth=Cookie(None)):
} }
@app.put("/users/{user_uuid}/display-name") @app.put("/orgs/{org_uuid}/users/{user_uuid}/display-name")
async def admin_update_user_display_name( async def admin_update_user_display_name(
user_uuid: UUID, payload: dict = Body(...), auth=Cookie(None) org_uuid: UUID, user_uuid: UUID, payload: dict = Body(...), auth=Cookie(None)
): ):
ctx = await permutil.session_context(auth)
try: try:
user_org, _role_name = await db.instance.get_user_organization(user_uuid) user_org, _role_name = await db.instance.get_user_organization(user_uuid)
except ValueError: except ValueError:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
if not ctx or not ( if user_org.uuid != org_uuid:
permutil.has_any(ctx, ["auth:admin"]) raise HTTPException(status_code=404, detail="User not found in organization")
or ( ctx = await authz.verify(
permutil.has_any(ctx, [f"auth:org:{user_org.uuid}"]) auth, ["auth:admin", f"auth:org:{org_uuid}"], match=permutil.has_any
and getattr(ctx.org, "uuid", None) == user_org.uuid )
) if (
"auth:admin" not in ctx.role.permissions
and f"auth:org:{org_uuid}" not in ctx.role.permissions
): ):
raise HTTPException(status_code=403, detail="Insufficient permissions") raise HTTPException(status_code=403, detail="Insufficient permissions")
new_name = (payload.get("display_name") or "").strip() new_name = (payload.get("display_name") or "").strip()
@ -420,18 +375,14 @@ async def admin_update_user_display_name(
@app.get("/permissions") @app.get("/permissions")
async def admin_list_permissions(auth=Cookie(None)): async def admin_list_permissions(auth=Cookie(None)):
ctx = await permutil.session_context(auth) await authz.verify(auth, ["auth:admin"], match=permutil.has_any)
if not ctx or not permutil.has_any(ctx, ["auth:admin", "auth:org:*"]):
raise ValueError("Insufficient permissions")
perms = await db.instance.list_permissions() perms = await db.instance.list_permissions()
return [{"id": p.id, "display_name": p.display_name} for p in perms] return [{"id": p.id, "display_name": p.display_name} for p in perms]
@app.post("/permissions") @app.post("/permissions")
async def admin_create_permission(payload: dict = Body(...), auth=Cookie(None)): async def admin_create_permission(payload: dict = Body(...), auth=Cookie(None)):
ctx = await permutil.session_context(auth) await authz.verify(auth, ["auth:admin"])
if not ctx or not permutil.has_any(ctx, ["auth:admin"]):
raise ValueError("Global admin required")
from ..db import Permission as PermDC from ..db import Permission as PermDC
perm_id = payload.get("id") perm_id = payload.get("id")
@ -447,9 +398,7 @@ async def admin_create_permission(payload: dict = Body(...), auth=Cookie(None)):
async def admin_update_permission( async def admin_update_permission(
permission_id: str, display_name: str, auth=Cookie(None) permission_id: str, display_name: str, auth=Cookie(None)
): ):
ctx = await permutil.session_context(auth) await authz.verify(auth, ["auth:admin"])
if not ctx or not permutil.has_any(ctx, ["auth:admin"]):
raise ValueError("Global admin required")
from ..db import Permission as PermDC from ..db import Permission as PermDC
if not display_name: if not display_name:
@ -463,9 +412,7 @@ async def admin_update_permission(
@app.post("/permission/rename") @app.post("/permission/rename")
async def admin_rename_permission(payload: dict = Body(...), auth=Cookie(None)): async def admin_rename_permission(payload: dict = Body(...), auth=Cookie(None)):
ctx = await permutil.session_context(auth) await authz.verify(auth, ["auth:admin"])
if not ctx or not permutil.has_any(ctx, ["auth:admin"]):
raise ValueError("Global admin required")
old_id = payload.get("old_id") old_id = payload.get("old_id")
new_id = payload.get("new_id") new_id = payload.get("new_id")
display_name = payload.get("display_name") display_name = payload.get("display_name")
@ -485,9 +432,7 @@ async def admin_rename_permission(payload: dict = Body(...), auth=Cookie(None)):
@app.delete("/permission") @app.delete("/permission")
async def admin_delete_permission(permission_id: str, auth=Cookie(None)): async def admin_delete_permission(permission_id: str, auth=Cookie(None)):
ctx = await permutil.session_context(auth) await authz.verify(auth, ["auth:admin"])
if not ctx or not permutil.has_any(ctx, ["auth:admin"]):
raise ValueError("Global admin required")
querysafe.assert_safe(permission_id, field="permission_id") querysafe.assert_safe(permission_id, field="permission_id")
await db.instance.delete_permission(permission_id) await db.instance.delete_permission(permission_id)
return {"status": "ok"} return {"status": "ok"}