Repository navigation
auth: extend Permission to cover chat, teams and webhooks, define key scopes as permission bundles, and return effective permissions from /me #16270
Description
Activity
- addedarea: auth-rbacWave 2 · cluster E — Auth, identity & RBAC canonWave 2 · cluster E — Auth, identity & RBAC canon
on Sep 11, 2026 Update: the frontend side of the four open decisions is dead code
I checked where the SPA actually uses each flagged string. None of the six has a real call site:
string where it appears settings:read,audit:read,audit:write,admin:organizationonly their definition in usePermissions.tssettings:write,users:readusePermissions.ts, plussrc/directives/permission.ts:20,23— inside a JSDoc comment, as usage examples forv-permissionNo template passes any of them to
v-permission,hasPermissionorcan. So the concern that drove two of the flags — that mappingsettings:*onto admin config would stop users changing their own settings — has nothing behind it. No view checks it.What that leaves, and the proposed resolution:
settings:*andusers:*matter only as API-key scopes, where the key's authority is intersected with its owner's. Proposed:settings:read/write→admin.config.read/write,users:read/write→admin.users.read/write. Both concepts already exist, and the intersection means a key only carries them if its owner does. Mapping onto them is an extension, not a fork.audit:*andadmin:organizationexist only in the frontend map and are checked nowhere. Proposed: no backend permission, and tech-debt(frontend): usePermissions' permission strings share nothing with the backend's Permission vocabulary #16243 drops them from the frontend rather than preserving dead strings.
These are routine mappings inside the owner's ruling, now backed by evidence, so they're recorded here for review rather than escalated. Object on this issue if any is wrong.
Correction: teams do have a backend
The issue body says no team-management write routes were found in
autobot-backend/api/. That search was too narrow —get_team_serviceexists atapi/user_management/dependencies.py:450, so teams are a real backend concept. Before choosing grants forteams.*, establish which routes use that service and what gates them today. The behaviour-preserving rule applies to it like the others.Decisions recorded (owner, 2026-09-11)
These settle this issue's four flagged decisions (AC6) and one question the investigation raised. Migration 062 had already mapped every colon string that is flagged here.
1. Chat and teams get dedicated members:
chat.use,chat.history, andteams.read/create/manage/delete. This replaces migration 062's fold (20260623_062_rbac_colon_to_dot_reconcile.py,COLON_TO_DOT), which mapschat:*toapi.read/api.writeandteams:*toadmin.users.*. The reason: a key scoped tochat:usemust not gainapi.write's reach. Migration 062 is applied history and stays as it is.2. The four flagged mappings adopt migration 062's choices:
scope permission settings:*admin.config.*users:*admin.users.*audit:writesecurity.manageadmin:organizationadmin.systemThe evidence behind them:
- "settings" is system config.
GETandPOST /api/settings/read and write the system config throughConfigService. Per-user preferences have their own routes:GET/PATCH /api/users/me/preferences(api/users.py:142,:173). audit:writeandadmin:organizationare defined inusePermissions.ts:55,:59and checked by no view.
3.
teams.*is granted to ADMIN only. This is a policy choice, not behaviour-preserving. Today any logged-in org member can manage teams in the main backend (#16276). The owner ruled that admin-only, to match the SLM backend'sADMIN_USERS_WRITEgate, and the grant follows that ruling.The team backend exists, which answers AC1's "if a team backend exists":
autobot-backend/api/user_management/teams.pyandautobot-slm-backend/api/autobot_teams.py.Also found during this investigation and filed: #16276 (team routes have no caller authorization), #16277 (
GET /teams/my-teamsis unreachable), a critical finding that/api/settingsis readable and writable anonymously, and a finding that/users/searchsearches across organisations for anonymous callers. The last two are being filed now.- "settings" is system config.
Correction to this issue's own Notes. They say the permissions.py comment near line 181 cites a test_permission_parity that does not exist under that name. That is wrong: autobot_shared/auth/test_permission_parity.py exists, and its test_all_roles_have_role_permissions_entry (line 58) asserts exactly what the comment says, that every Role has a ROLE_PERMISSIONS entry. The reference is correct and stays as it is. roles_are_canonical_test.py:158 asserts the same thing independently.
- added 4 commits that reference this issue
on Sep 11, 2026 - added a commit that references this issue
on Sep 13, 2026 Closure evidence (auto-closed by #16298, merge
fe7b58f80). Every acceptance criterion is verified againstorigin/main:# Criterion Evidence on main 1 New Permissionmembers for chat, webhooks and teamsautobot_shared/auth/permissions.py:85-91:CHAT_USE = "chat.use",CHAT_HISTORY,TEAMS_READ/CREATE/MANAGE/DELETE,WEBHOOKS_TRIGGER = "webhooks.trigger". The team backend exists (api/user_management/teams.py), per the 2026-09-11 ruling.2 Every API_KEY_SCOPESscope has a non-empty bundle, held in one placeSCOPE_PERMISSIONSlives inautobot_shared/auth/key_scopes.py.key_scopes_test.py:14assertsset(SCOPE_PERMISSIONS) == set(API_KEY_SCOPES), and:21asserts that no scope maps to an empty bundle ("scopes that would grant nothing").3 New members in ROLE_PERMISSIONS[Role.ADMIN], in noSUPERADMINentry, androles_are_canonical_testunmodifiedpermissions.py:341-348lists all seven under admin, and:389isRole.SUPERADMIN: [].roles_are_canonical_test.pywas last changed on 2026-08-24 (b17468aa9), and mergefe7b58f80does not touch it.4 Each grant recorded as behaviour-preserving or a policy choice permissions.py:289-293: chat.* are behaviour-preserving, because every chat route is login-only. teams.* and webhooks.trigger are a policy choice, admin only, per the #16276 ruling. The same record is in #16298's "Grant record" table.5 GET /mereturnspermissionsandis_admin, and superadmin gets[]andtrueautobot-backend/api/auth_me.py:81-82returnseffective_permissions(role)andis_admin_role(role).auth_me_test.py:32test_a_superadmin_gets_no_granular_permissions_and_is_adminassertsbody["permissions"] == []andbody["is_admin"] is True.6 The four flagged decisions settled here before merge 2026-09-11 06:26Z ruling, about 42 h before the merge: settings:*→admin.config.*,users:*→admin.users.*,audit:write→security.manage,admin:organization→admin.system.Carried to #16243 / #16493: #16298 mapped neither
audit:writenoradmin:organization, because neither is an API-key scope. Its body says "#16243 retires them", which follows the earlier proposal, not the 06:26Z ruling. No conflicting mapping was merged, so criterion 6 holds. #16493 will state how the frontend treats those two concepts. Stated gaps, tracked elsewhere: existing deployments'permissionstables are not seeded with the new members (#16295), and there is no HTTP-levelrequire_key_permissiontest yet (#16294).Post-merge audit: all 6 acceptance criteria verified on
origin/mainPR #16298, merge commit
fe7b58f80. The issue stays closed.AC Evidence on origin/main1. New Permissionmembersautobot_shared/auth/permissions.py:85-91:CHAT_USE = "chat.use",CHAT_HISTORY,TEAMS_READ/CREATE/MANAGE/DELETE,WEBHOOKS_TRIGGER = "webhooks.trigger"2. Every scope has a bundle, held in one place, and none is empty autobot_shared/auth/key_scopes.py:34SCOPE_PERMISSIONS.key_scopes_test.py:14test_every_published_scope_has_a_bundle_and_nothing_else_doeschecks that it equalsAPI_KEY_SCOPES, and:21test_no_scope_maps_to_an_empty_bundle.3. In ROLE_PERMISSIONS[Role.ADMIN], in noSUPERADMINentry, canonical test passes unmodifiedThe new members are at permissions.py:342-348, inside theRole.ADMINlist that opens at:295.:389isRole.SUPERADMIN: [].roles_are_canonical_test.pyis not infe7b58f80's diff. PR CI: all 13 python-suite checks passed.4. Grants recorded as behaviour-preserving or policy The comment at permissions.py:288-293.chat.useandchat.historyare behaviour-preserving, because every chat route is login-only today.teams.*andwebhooks.triggerare a policy choice: admin only.5. GET /mereturnspermissionsandis_admin; superadmin testautobot-backend/api/auth_me.py:36-37(model fields) and:81-82.auth_me_test.py:32test_a_superadmin_gets_no_granular_permissions_and_is_adminassertspermissions == []andis_admin is True.6. Four flagged decisions settled and recorded here before merge The owner's comment "Decisions recorded (owner, 2026-09-11)" on this issue, at 2026-09-11 09:26 Riga time. The PR merged at 2026-09-13 03:45 Riga time. - added a commit that references this issue
on Sep 13, 2026 Closure evidence (batch 2c)
Re-verified the 6 already-ticked ACs against
origin/main(referenced by PR #16493, 79c6344). All hold.AC Evidence New Permissionmembersautobot_shared/auth/permissions.py:85-91CHAT_USE = "chat.use"…TEAMS_*…WEBHOOKS_TRIGGER = "webhooks.trigger"Every API_KEY_SCOPESentry has a bundleautobot_shared/auth/key_scopes.py:34SCOPE_PERMISSIONS(14 scopes); testskey_scopes_test.py::test_every_published_scope_has_a_bundle_and_nothing_else_does,test_no_scope_maps_to_an_empty_bundleNew members in ROLE_PERMISSIONS[Role.ADMIN], none in SUPERADMINpermissions.py:341-348(admin holds 62 of 62);:389Role.SUPERADMIN: []Grants recorded as behaviour-preserving or policy choice permissions.py:289-293"chat.use / chat.history are behaviour-preserving… teams.* and webhooks.trigger are a policy choice: admin only"GET /mereturnspermissionsandis_admin; superadmin testautobot-backend/api/auth_me.py:36-37; testauth_me_test.py::test_a_superadmin_gets_no_granular_permissions_and_is_adminassertspermissions == []andis_admin is TrueThe four flagged decisions settled before the mappings The owner's ruling was posted here on 2026-09-11 (Riga time), before the mappings merged on 2026-09-13; key_scopes.pymapssettings:read→ADMIN_CONFIG_READandusers:read→ADMIN_USERS_READ, as ruled
Decision this implements
The repo owner ruled (recorded on #16040):
Permissionis the single vocabulary of authority. It is extended to model the concepts API-key scopes and the frontend already use, and each scope inAPI_KEY_SCOPESbecomes a defined bundle of permissions.GET /mereturns a user's effective permissions, and the frontend's own role→permission map is retired (tech-debt(frontend): usePermissions' permission strings share nothing with the backend's Permission vocabulary #16243).This is the foundation both depend on. #16040's AC2 — a key's authority is its owner's permissions intersected with its scopes — cannot be computed while the two sides use different vocabularies.
The gap, measured
Permission(autobot_shared/auth/permissions.py:29) is dot strings.API_KEY_SCOPES(autobot_shared/user_management/models/api_key.py:230) is 14 colon strings.Permissionknowledge:read/write/deleteknowledge.read/write/deletefiles:deletefiles.deletefiles:readfiles.view,files.downloadfiles:writefiles.upload,files.manageadmin:*Permissionmember — mirrorsRole.ADMINchat:use,chat:historychat.use,chat.historywebhooks:triggerwebhooks.triggerusers:read/writeadmin.users.read/write— flagged, see belowsettings:read/writeadmin.config.read/write— flagged, see belowThe frontend's
usePermissions.tsaddsteams:*(absent → newteams.read/create/manage/delete),audit:*andadmin:access/organization/users.Extend, don't fork. Where a concept already exists, map onto it. Adding
audit.readbesidesecurity.audit, orusers.*besideadmin.users.*, would create a second name for one authority.Constraints the change must honour
These are recorded in
permissions.pyitself:ROLE_PERMISSIONS, done — both backends pick it up on deploy.ROLE_PERMISSIONS[Role.ADMIN]must hold every member, asserted byroles_are_canonical_test. Every new member goes into admin's list.SUPERADMINholds no granular permissions, deliberately (security(rbac): superadmin is administrative but is not a Role member and holds no permissions #13854, tech-debt(auth): "superadmin" is used at 17 require_role call sites but is not a member of the canonical Role enum #12786). It is an administrative predicate admitted byrequire_roleandis_admin_role, not a permission holder. New members must not be granted to it.role_has_permissionanswers only fromROLE_PERMISSIONS, with no admin short-circuit.Grants must preserve today's behaviour
A new member is granted to no role until someone grants it. The moment #16040 AC4 enforces a permission on a route, any role without it loses that capability. So each new permission is granted to exactly the roles that effectively hold the capability today. Tightening is a separate, deliberate act — the same principle the superadmin entry states.
What is known so far:
chat.useandchat.historyto all interactive roles.autobot-backend/api/. Establish whether they exist inautobot-slm-backendor anywhere, before choosing grants.api/telegram_bot.py,api/whatsapp.py) are inbound, authenticated by provider secrets, not by user permission. No route was found that enforceswebhooks:trigger. A permission that gates nothing today is fine to add, but its grants are then a policy choice, not behaviour preservation, and must be recorded as such.GET /meIt returns
username,role,email,auth_method,authenticated,deployment_modetoday (api/auth.py:300). Add:permissions— computed fromROLE_PERMISSIONS, the same sourcerole_has_permissionreads, never a second copy of the ruleis_admin— fromis_admin_role. This is required, not optional: a superadmin'spermissionsis the empty list by design, so a frontend gating only onpermissionswould hide everything from superadmins. The frontend must mirror both kinds of server check.Decisions this issue still needs
Flagged rather than guessed, because each changes who can do what:
settings:*→admin.config.*is right only if "application settings" means system configuration. If the frontend's settings are per-user preferences, mapping them onto admin config would stop users changing their own settings, and they need their own permission.users:read→admin.users.readmeans no non-admin can read user information. Correct for user-management screens; wrong if anything shows other users' names to ordinary users.audit:writehas no obvious counterpart —security.auditis a single read-style permission.admin:organizationhas no counterpart at all.Acceptance criteria
Permissionmembers exist for concepts with no existing counterpart — at minimumchat.use,chat.history,webhooks.trigger, andteams.*if a team backend exists.API_KEY_SCOPEShas a defined bundle ofPermissionmembers, held in one place, and a test asserts no scope maps to an empty bundle.ROLE_PERMISSIONS[Role.ADMIN]and in noSUPERADMINentry;roles_are_canonical_testpasses unmodified.GET /mereturnspermissionsandis_admin; a test asserts a superadmin gets an emptypermissionsandis_admin: true.Notes
test_permission_paritythat does not exist under that name. The real assertions are inroles_are_canonical_test.py,permissions_test.py,canonical_permissions_test.pyandauth_rbac_test.py. Fix the reference while here._SECRETS_VAULT_LEGACYalready keeps colon-style secrets permissions outside the enum as a documented exception (Umbrella/PRD: unify AutoBot secrets — one RBAC-governed store, vaults + one key abstraction (replaces 11 stores, 5 keys, 2 UIs) #10088). Out of scope — but it is a third place colon permission names live.Blocks #16040 and #16243.