feat: migrate agents-access to org-scoped system role for proper chat RBAC (#24438)

The agents-access role previously granted chat permissions at user
scope, but chats are org-scoped objects. Rego skips user-level perms
when org_owner is set, making the grants invisible. Handler-level
band-aids used synthetic non-org-scoped objects as a workaround.

  - Migrates agents-access from users.rbac_roles (site-level) to
    organization_members.roles (org-scoped) via DB migration
  - Redefines agents-access as a predefined org-scoped builtin role
    alongside organization-admin, organization-auditor, etc., with
    Member permissions granting chat create/read/update
  - Excludes ResourceChat from OrgMemberPermissions so org membership
    alone no longer grants chat access
  - Fixes handler Authorize checks to use org-scoped objects with
semantically correct actions (ActionUpdate for message/tool operations)
  - Grants org admins the ability to assign agents-access

Closes #24250
Fixes CODAGT-174

Note: this does not update the "Usage" endpoints. Tracked by CODAGT-161.
> 🤖
This commit is contained in:
Cian Johnston
2026-04-23 17:59:42 +01:00
committed by GitHub
parent 7efccfa996
commit b5a625549e
19 changed files with 441 additions and 174 deletions
+10 -12
View File
@@ -1137,14 +1137,13 @@ func TestCreateChatNonDefaultOrg(t *testing.T) {
// Create a second (non-default) org via the API.
secondOrg := coderdenttest.CreateOrganization(t, client, coderdenttest.CreateOrganizationOptions{})
// Create a member in the default org, then add them to the second org.
// Create a member with agents-access in both orgs.
memberClientRaw, member := coderdtest.CreateAnotherUser(
t, client, firstUser.OrganizationID, rbac.RoleAgentsAccess(),
t, client, firstUser.OrganizationID,
rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID),
rbac.ScopedRoleAgentsAccess(secondOrg.ID),
)
_, err = client.PostOrganizationMember(ctx, secondOrg.ID, member.Username)
require.NoError(t, err)
memberClient := codersdk.NewExperimentalClient(memberClientRaw)
// Create a chat in the non-default org.
chat, err := memberClient.CreateChat(ctx, codersdk.CreateChatRequest{
OrganizationID: secondOrg.ID,
@@ -1215,14 +1214,13 @@ func TestListChats_OrgAdminOnlySeesOwnChats(t *testing.T) {
// Create a second (non-default) org.
secondOrg := coderdenttest.CreateOrganization(t, client, coderdenttest.CreateOrganizationOptions{})
// Create a regular member with agents access in the second org.
memberClientRaw, member := coderdtest.CreateAnotherUser(
t, client, firstUser.OrganizationID, rbac.RoleAgentsAccess(),
// Create a member with agents-access in both orgs.
memberClientRaw, _ := coderdtest.CreateAnotherUser(
t, client, firstUser.OrganizationID,
rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID),
rbac.ScopedRoleAgentsAccess(secondOrg.ID),
)
_, err = client.PostOrganizationMember(ctx, secondOrg.ID, member.Username)
require.NoError(t, err)
memberExp := codersdk.NewExperimentalClient(memberClientRaw)
// Member creates a chat in the second org.
memberChat, err := memberExp.CreateChat(ctx, codersdk.CreateChatRequest{
OrganizationID: secondOrg.ID,
@@ -1239,7 +1237,7 @@ func TestListChats_OrgAdminOnlySeesOwnChats(t *testing.T) {
// Create an org admin in the second org with agents access.
adminClientRaw, _ := coderdtest.CreateAnotherUser(
t, client, firstUser.OrganizationID,
rbac.ScopedRoleOrgAdmin(secondOrg.ID), rbac.RoleAgentsAccess(),
rbac.ScopedRoleOrgAdmin(secondOrg.ID), rbac.ScopedRoleAgentsAccess(secondOrg.ID),
)
adminExp := codersdk.NewExperimentalClient(adminClientRaw)
+3 -3
View File
@@ -493,7 +493,6 @@ func TestListRoles(t *testing.T) {
{Name: codersdk.RoleAuditor}: false,
{Name: codersdk.RoleTemplateAdmin}: false,
{Name: codersdk.RoleUserAdmin}: false,
{Name: codersdk.RoleAgentsAccess}: false,
}),
},
{
@@ -507,6 +506,7 @@ func TestListRoles(t *testing.T) {
{Name: codersdk.RoleOrganizationTemplateAdmin, OrganizationID: owner.OrganizationID}: false,
{Name: codersdk.RoleOrganizationUserAdmin, OrganizationID: owner.OrganizationID}: false,
{Name: codersdk.RoleOrganizationWorkspaceCreationBan, OrganizationID: owner.OrganizationID}: false,
{Name: codersdk.RoleAgentsAccess, OrganizationID: owner.OrganizationID}: false,
}),
},
{
@@ -527,7 +527,6 @@ func TestListRoles(t *testing.T) {
{Name: codersdk.RoleAuditor}: false,
{Name: codersdk.RoleTemplateAdmin}: false,
{Name: codersdk.RoleUserAdmin}: false,
{Name: codersdk.RoleAgentsAccess}: false,
}),
},
{
@@ -541,6 +540,7 @@ func TestListRoles(t *testing.T) {
{Name: codersdk.RoleOrganizationTemplateAdmin, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleOrganizationUserAdmin, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleOrganizationWorkspaceCreationBan, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleAgentsAccess, OrganizationID: owner.OrganizationID}: true,
}),
},
{
@@ -561,7 +561,6 @@ func TestListRoles(t *testing.T) {
{Name: codersdk.RoleAuditor}: true,
{Name: codersdk.RoleTemplateAdmin}: true,
{Name: codersdk.RoleUserAdmin}: true,
{Name: codersdk.RoleAgentsAccess}: true,
}),
},
{
@@ -575,6 +574,7 @@ func TestListRoles(t *testing.T) {
{Name: codersdk.RoleOrganizationTemplateAdmin, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleOrganizationUserAdmin, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleOrganizationWorkspaceCreationBan, OrganizationID: owner.OrganizationID}: true,
{Name: codersdk.RoleAgentsAccess, OrganizationID: owner.OrganizationID}: true,
}),
},
}