plugins: installing one grants it to every role
The default was deny-everything: a newly installed plugin reached nobody, and every grant had to be added by hand, per role, on a different screen, by the owner. That made sense while the permission model was being built and there were no members. It stopped making sense once installing a plugin became a deliberate administrative act — somebody who installs a thing has already decided the server should have it, and a second decision elsewhere has one reliable outcome: it gets forgotten and the plugin looks broken. So install grants, the owner revokes. uninstallPlugin already removed those grants, so the pair is symmetric. ROWS, NOT A RULE CHANGE. authorize.ts still denies on absence. The other way to get this behaviour is to make absence mean allow, and that is the version that bites in a year: every capability with no row for any reason — a failed write, a half-finished install, a bug — becomes permitted and nothing looks wrong. The blast radius here is only ever a plugin's own keys; core, execution, admin and confined are unreachable from it. Super Admin is skipped because ck_role_permissions_not_owner refuses it outright and would fail the whole insert. The owner bypasses grants anyway. Also fixes something older that would have made this half-true. A role created through /api/roles began with NOTHING — DEFAULT_ROLE_PERMISSIONS was only ever applied in bootstrap, so a new role could not reach chat, files or terminal while Member could, and nothing said why. A new role now starts where Member does, plus every installed plugin. Leaving it would mean the policy held for roles that existed when a plugin was installed and not for any created after, which reads as a bug in whichever half you meet first. Verified against the live database with a throwaway role and a throwaway plugin: the new role seeded with the confined baseline plus every installed plugin, the grant reached all four roles and never Super Admin, and cleanup left the table exactly as it was. 1417 pass. NOT done: existing installs. browser, email and offscale are installed here and granted to nobody, because they predate this. A backfill is one query and is the owner's call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -2,6 +2,7 @@ import { createRouter } from '../../create-router';
|
||||
import * as errors from '@@/custom-errors';
|
||||
import { isSuperAdmin } from '../../super-admin';
|
||||
import { listRoles, createRole, renameRole, deleteRole, setRoleGrants } from 'officerdb';
|
||||
import { seedGrantsForNewRole } from '@@/plugins/grant-on-install';
|
||||
|
||||
/*
|
||||
/api/roles — role CRUD (phase 2 part 3 of docs/roles-and-teams.md), plus grants (phase 3).
|
||||
@@ -70,7 +71,20 @@ rolesRouter.post('/', async (ctx) => {
|
||||
? errors.BAD_REQUEST('a role name cannot be empty')
|
||||
: errors.CONFLICT(`a role named '${body.name.trim()}' already exists`);
|
||||
}
|
||||
return ctx.json({ role: result.role });
|
||||
/*
|
||||
A new role starts where Member does: the confined baseline plus every installed plugin.
|
||||
|
||||
Not doing this is how the policy ends up half-applied — granted to roles that existed when a plugin was
|
||||
installed, and not to any created afterwards. It also fixes something older: DEFAULT_ROLE_PERMISSIONS
|
||||
was only ever applied at bootstrap, so a role created here used to begin with nothing at all, not even
|
||||
chat or files, and nothing said why.
|
||||
|
||||
Not fatal. The role exists either way and its grants can be set on the permissions screen; failing the
|
||||
creation over a grant would be the worse trade.
|
||||
*/
|
||||
const seeded = await seedGrantsForNewRole(result.role.name).catch(() => 0);
|
||||
|
||||
return ctx.json({ role: result.role, seededGrants: seeded });
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user