From b0ff6615d4a8c9ec8bcb1a4f0f970c424653c187 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Fri, 14 Aug 2026 19:52:10 +0000 Subject: [PATCH] feat: enable Coder Agents for organization members --- .../000570_remove_agents_access_role.down.sql | 9 + .../000570_remove_agents_access_role.up.sql | 11 + coderd/database/migrations/migrate_test.go | 50 ++++ coderd/database/querier_test.go | 12 +- coderd/exp_chats_acl_test.go | 4 +- coderd/exp_chats_test.go | 162 +++-------- coderd/rbac/roles.go | 48 +--- coderd/rbac/roles_test.go | 252 ++++++++---------- codersdk/rbacroles.go | 1 - docs/ai-coder/agents/getting-started.md | 66 +---- docs/reference/glossary.md | 5 - enterprise/coderd/exp_chats_test.go | 11 +- enterprise/coderd/roles_test.go | 3 - site/site_test.go | 43 ++- site/src/api/typesGenerated.ts | 6 - .../dashboard/Navbar/NavbarView.stories.tsx | 4 +- .../modules/roles/RoleSelector.stories.tsx | 6 +- site/src/modules/roles/index.ts | 2 - .../components/AgentCreateForm.stories.tsx | 23 +- .../AgentsPage/components/AgentCreateForm.tsx | 3 +- .../components/ChatAccessDeniedAlert.tsx | 9 +- .../CustomRolesPageView.stories.tsx | 11 - site/src/testHelpers/entities.ts | 10 - 23 files changed, 274 insertions(+), 477 deletions(-) create mode 100644 coderd/database/migrations/000570_remove_agents_access_role.down.sql create mode 100644 coderd/database/migrations/000570_remove_agents_access_role.up.sql diff --git a/coderd/database/migrations/000570_remove_agents_access_role.down.sql b/coderd/database/migrations/000570_remove_agents_access_role.down.sql new file mode 100644 index 0000000000000..67f046a027844 --- /dev/null +++ b/coderd/database/migrations/000570_remove_agents_access_role.down.sql @@ -0,0 +1,9 @@ +-- Restore effective chat access when rolling back to a version that requires +-- the role. The previous assignments cannot be recovered after migration up. +UPDATE organization_members +SET roles = array_append(roles, 'agents-access') +WHERE NOT ('agents-access' = ANY(roles)); + +UPDATE organizations +SET default_org_member_roles = array_append(default_org_member_roles, 'agents-access') +WHERE NOT ('agents-access' = ANY(default_org_member_roles)); diff --git a/coderd/database/migrations/000570_remove_agents_access_role.up.sql b/coderd/database/migrations/000570_remove_agents_access_role.up.sql new file mode 100644 index 0000000000000..086d97045e9be --- /dev/null +++ b/coderd/database/migrations/000570_remove_agents_access_role.up.sql @@ -0,0 +1,11 @@ +UPDATE users +SET rbac_roles = array_remove(rbac_roles, 'agents-access') +WHERE 'agents-access' = ANY(rbac_roles); + +UPDATE organization_members +SET roles = array_remove(roles, 'agents-access') +WHERE 'agents-access' = ANY(roles); + +UPDATE organizations +SET default_org_member_roles = array_remove(default_org_member_roles, 'agents-access') +WHERE 'agents-access' = ANY(default_org_member_roles); diff --git a/coderd/database/migrations/migrate_test.go b/coderd/database/migrations/migrate_test.go index df0c7d14ea9bb..be8ae221e2ea4 100644 --- a/coderd/database/migrations/migrate_test.go +++ b/coderd/database/migrations/migrate_test.go @@ -1190,6 +1190,56 @@ func TestMigration000475AgentsAccessOrgRole(t *testing.T) { ) } +func TestMigration000570RemoveAgentsAccessRole(t *testing.T) { + t.Parallel() + + const migrationVersion = 570 + + sqlDB := testSQLDB(t) + next, err := migrations.Stepper(sqlDB) + require.NoError(t, err) + for { + version, more, err := next() + require.NoError(t, err) + if !more { + t.Fatalf("migration %d not found", migrationVersion) + } + if version == migrationVersion-1 { + break + } + } + + db := database.New(sqlDB) + user := dbgen.User(t, db, database.User{ + RBACRoles: []string{"auditor", "agents-access"}, + }) + org := dbgen.Organization(t, db, database.Organization{ + DefaultOrgMemberRoles: []string{"organization-workspace-access", "agents-access"}, + }) + dbgen.OrganizationMember(t, db, database.OrganizationMember{ + OrganizationID: org.ID, + UserID: user.ID, + Roles: []string{"organization-auditor", "agents-access"}, + }) + + version, _, err := next() + require.NoError(t, err) + require.EqualValues(t, migrationVersion, version) + + ctx := testutil.Context(t, testutil.WaitLong) + var siteRoles, orgRoles, defaultRoles pq.StringArray + err = sqlDB.QueryRowContext(ctx, "SELECT rbac_roles FROM users WHERE id = $1", user.ID).Scan(&siteRoles) + require.NoError(t, err) + err = sqlDB.QueryRowContext(ctx, "SELECT roles FROM organization_members WHERE organization_id = $1 AND user_id = $2", org.ID, user.ID).Scan(&orgRoles) + require.NoError(t, err) + err = sqlDB.QueryRowContext(ctx, "SELECT default_org_member_roles FROM organizations WHERE id = $1", org.ID).Scan(&defaultRoles) + require.NoError(t, err) + + require.Equal(t, []string{"auditor"}, []string(siteRoles)) + require.Equal(t, []string{"organization-auditor"}, []string(orgRoles)) + require.Equal(t, []string{"organization-workspace-access"}, []string(defaultRoles)) +} + func TestMigration000504AIProvidersBackfill(t *testing.T) { t.Parallel() diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 44fc384b6197d..b3434c0ef7079 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -1464,8 +1464,8 @@ func TestGetAuthorizedChats(t *testing.T) { org := dbgen.Organization(t, db, database.Organization{}) dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: owner.ID, OrganizationID: org.ID}) - dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: member.ID, OrganizationID: org.ID, Roles: []string{rbac.RoleAgentsAccess()}}) - dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: secondMember.ID, OrganizationID: org.ID, Roles: []string{rbac.RoleAgentsAccess()}}) + dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: member.ID, OrganizationID: org.ID}) + dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: secondMember.ID, OrganizationID: org.ID}) // Create FK dependencies: a chat provider and model config. _ = dbgen.ChatProvider(t, db, database.ChatProvider{ @@ -1652,7 +1652,7 @@ func TestGetAuthorizedChats(t *testing.T) { // Use a dedicated user for pagination to avoid interference // with the other parallel subtests. paginationUser := dbgen.User(t, db, database.User{}) - dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: paginationUser.ID, OrganizationID: org.ID, Roles: []string{rbac.RoleAgentsAccess()}}) + dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: paginationUser.ID, OrganizationID: org.ID}) for i := range 7 { dbgen.Chat(t, db, database.Chat{ OrganizationID: org.ID, @@ -1726,12 +1726,10 @@ func TestGetAuthorizedChatsACLSharing(t *testing.T) { dbgen.OrganizationMember(t, db, database.OrganizationMember{ UserID: owner.ID, OrganizationID: org.ID, - Roles: []string{rbac.RoleAgentsAccess()}, }) dbgen.OrganizationMember(t, db, database.OrganizationMember{ UserID: recipient.ID, OrganizationID: org.ID, - Roles: []string{rbac.RoleAgentsAccess()}, }) dbgen.ChatProvider(t, db, database.ChatProvider{Provider: "openai", DisplayName: "OpenAI"}) @@ -1844,12 +1842,10 @@ func TestGetAuthorizedChatsACLSharingGroupACL(t *testing.T) { dbgen.OrganizationMember(t, db, database.OrganizationMember{ UserID: owner.ID, OrganizationID: org.ID, - Roles: []string{rbac.RoleAgentsAccess()}, }) dbgen.OrganizationMember(t, db, database.OrganizationMember{ UserID: recipient.ID, OrganizationID: org.ID, - Roles: []string{rbac.RoleAgentsAccess()}, }) group := dbgen.Group(t, db, database.Group{OrganizationID: org.ID}) dbgen.GroupMember(t, db, database.GroupMemberTable{UserID: recipient.ID, GroupID: group.ID}) @@ -1948,12 +1944,10 @@ func TestGetAuthorizedChatsByChatFileIDACLSharing(t *testing.T) { dbgen.OrganizationMember(t, db, database.OrganizationMember{ UserID: owner.ID, OrganizationID: org.ID, - Roles: []string{rbac.RoleAgentsAccess()}, }) dbgen.OrganizationMember(t, db, database.OrganizationMember{ UserID: recipient.ID, OrganizationID: org.ID, - Roles: []string{rbac.RoleAgentsAccess()}, }) dbgen.ChatProvider(t, db, database.ChatProvider{Provider: "openai", DisplayName: "OpenAI"}) diff --git a/coderd/exp_chats_acl_test.go b/coderd/exp_chats_acl_test.go index e3bd2958e70a7..ee96553076a16 100644 --- a/coderd/exp_chats_acl_test.go +++ b/coderd/exp_chats_acl_test.go @@ -463,7 +463,7 @@ func TestListChatsSharedScope(t *testing.T) { client, db := newChatClientWithDatabase(t) firstUser := coderdtest.CreateFirstUser(t, client.Client) modelConfig := createChatModelConfig(t, client) - viewerClient, viewer := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + viewerClient, viewer := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) viewerClientExp := codersdk.NewExperimentalClient(viewerClient) sharedChat := dbgen.Chat(t, db, database.Chat{ OrganizationID: firstUser.OrganizationID, @@ -562,7 +562,7 @@ func TestChatSharingDisabled(t *testing.T) { }) firstUser := coderdtest.CreateFirstUser(t, client.Client) modelConfig := createChatModelConfig(t, client) - viewerClient, viewer := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + viewerClient, viewer := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) viewerClientExp := codersdk.NewExperimentalClient(viewerClient) chat := dbgen.Chat(t, store, database.Chat{ diff --git a/coderd/exp_chats_test.go b/coderd/exp_chats_test.go index da87e267766a0..2a7f91e87c70d 100644 --- a/coderd/exp_chats_test.go +++ b/coderd/exp_chats_test.go @@ -298,9 +298,7 @@ func TestPostChats(t *testing.T) { firstUser := coderdtest.CreateFirstUser(t, client.Client) modelConfig := createChatModelConfig(t, client) - // Use a member with agents-access instead of the owner to - // verify least-privilege access. - memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) chat, err := memberClient.CreateChat(ctx, codersdk.CreateChatRequest{ @@ -352,30 +350,6 @@ func TestPostChats(t *testing.T) { })) }) - t.Run("MemberWithoutAgentsAccess", func(t *testing.T) { - t.Parallel() - - ctx := testutil.Context(t, testutil.WaitLong) - client := newChatClient(t) - firstUser := coderdtest.CreateFirstUser(t, client.Client) - _ = createChatModelConfig(t, client) - - // Member without agents-access should be denied. - memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) - memberClient := codersdk.NewExperimentalClient(memberClientRaw) - - _, err := memberClient.CreateChat(ctx, codersdk.CreateChatRequest{ - OrganizationID: firstUser.OrganizationID, - Content: []codersdk.ChatInputPart{ - { - Type: codersdk.ChatInputPartTypeText, - Text: "this should fail", - }, - }, - }) - requireSDKError(t, err, http.StatusForbidden) - }) - t.Run("WithReasoningEffort", func(t *testing.T) { t.Parallel() @@ -652,7 +626,7 @@ func TestPostChats(t *testing.T) { ctx := testutil.Context(t, testutil.WaitLong) adminClient, db := newChatClientWithDatabase(t) firstUser := coderdtest.CreateFirstUser(t, adminClient.Client) - memberClientRaw, _ := coderdtest.CreateAnotherUser(t, adminClient.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + memberClientRaw, _ := coderdtest.CreateAnotherUser(t, adminClient.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) workspaceBuild := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ @@ -689,7 +663,6 @@ func TestPostChats(t *testing.T) { adminClient.Client, firstUser.OrganizationID, rbac.ScopedRoleOrgAdmin(firstUser.OrganizationID), - rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID), ) orgAdminClient := codersdk.NewExperimentalClient(orgAdminClientRaw) @@ -857,7 +830,7 @@ func TestPostChats(t *testing.T) { firstUser := coderdtest.CreateFirstUser(t, client.Client) _ = createChatModelConfig(t, client) - memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) _, err := memberClient.CreateChat(ctx, codersdk.CreateChatRequest{ @@ -879,7 +852,7 @@ func TestPostChats(t *testing.T) { firstUser := coderdtest.CreateFirstUser(t, client.Client) _ = createChatModelConfig(t, client) - memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) // Create a second organization via the database since the @@ -960,7 +933,7 @@ func TestChats_ForceOnMCPServerEnforced(t *testing.T) { // A regular member tampers with the request by clearing // mcp_server_ids (Cure53 CDM-02-010 reproduction). - memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) chat, err := memberClient.CreateChat(ctx, codersdk.CreateChatRequest{ @@ -999,7 +972,7 @@ func TestPostChats_ClientType(t *testing.T) { firstUser := coderdtest.CreateFirstUser(t, client.Client) _ = createChatModelConfig(t, client) - memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) newChat := func(t *testing.T, clientType codersdk.ChatClientType) codersdk.Chat { @@ -1104,7 +1077,7 @@ func TestListChats(t *testing.T) { }) require.NoError(t, err) - memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) memberDBChat := dbgen.Chat(t, db, database.Chat{ OrganizationID: firstUser.OrganizationID, @@ -1178,7 +1151,7 @@ func TestListChats(t *testing.T) { modelConfig := createChatModelConfig(t, client) ownerClientRaw, owner := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.RoleOwner()) ownerClient := codersdk.NewExperimentalClient(ownerClientRaw) - memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) ownedChat := dbgen.Chat(t, db, database.Chat{ @@ -1254,18 +1227,13 @@ func TestListChats(t *testing.T) { require.NotContains(t, memberChatIDs, unsharedReadableChat.ID) }) - t.Run("OrgMemberWithoutAgentsAccessCannotAccessOwnChats", func(t *testing.T) { + t.Run("OrgMemberCanAccessOwnChats", func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) client, db := newChatClientWithDatabase(t) firstUser := coderdtest.CreateFirstUser(t, client.Client) modelConfig := createChatModelConfig(t, client) - // Create a member without agents-access and insert a chat - // owned by them via system context. Without agents-access, - // the member has no ResourceChat permissions at all, so - // listing returns 0 chats (SQL auth filter) and getting - // a specific chat returns 404 (dbauthz wraps as not found). memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) chat := dbgen.Chat(t, db, database.Chat{ @@ -1275,18 +1243,15 @@ func TestListChats(t *testing.T) { Title: "member chat", }) - // Listing chats returns empty because the SQL auth - // filter excludes chats the member cannot read. chats, err := memberClient.ListChats(ctx, nil) require.NoError(t, err) - require.Len(t, chats, 0) + require.Len(t, chats, 1) + require.Equal(t, chat.ID, chats[0].ID) - // Getting a specific chat returns 404 because dbauthz - // wraps authorization failures as not-found. err = memberClient.UpdateChat(ctx, chat.ID, codersdk.UpdateChatRequest{ Title: ptr.Ref("new title"), }) - requireSDKError(t, err, http.StatusNotFound) + require.NoError(t, err) }) t.Run("Unauthenticated", func(t *testing.T) { @@ -4990,7 +4955,7 @@ func TestGetChat(t *testing.T) { }) require.NoError(t, err) - otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) otherClient := codersdk.NewExperimentalClient(otherClientRaw) _, err = otherClient.GetChat(ctx, createdChat.ID) requireSDKError(t, err, http.StatusNotFound) @@ -6970,7 +6935,7 @@ func TestPostChatMessages(t *testing.T) { require.Equal(t, "No default chat model config is configured.", sdkErr.Message) }) - t.Run("MemberWithoutAgentsAccess", func(t *testing.T) { + t.Run("MemberCanCreateChatMessage", func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) @@ -6978,11 +6943,6 @@ func TestPostChatMessages(t *testing.T) { firstUser := coderdtest.CreateFirstUser(t, client.Client) modelConfig := createChatModelConfig(t, client) - // Create a member without agents-access and insert a - // chat owned by them via system context. Without - // agents-access the member has no ResourceChat - // permissions, so the ChatParam middleware returns 404 - // before the handler can check agents-access. memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) chat := dbgen.Chat(t, db, database.Chat{ @@ -6996,11 +6956,11 @@ func TestPostChatMessages(t *testing.T) { Content: []codersdk.ChatInputPart{ { Type: codersdk.ChatInputPartTypeText, - Text: "this should fail", + Text: "hello from member", }, }, }) - requireSDKError(t, err, http.StatusNotFound) + require.NoError(t, err) }) t.Run("EmptyText", func(t *testing.T) { @@ -9502,7 +9462,7 @@ func TestRegenerateChatTitle(t *testing.T) { }) require.NoError(t, err) - otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) otherClient := codersdk.NewExperimentalClient(otherClientRaw) _, err = otherClient.RegenerateChatTitle(ctx, createdChat.ID) requireSDKError(t, err, http.StatusNotFound) @@ -10246,7 +10206,7 @@ func TestGetChatDiffStatus(t *testing.T) { }) require.NoError(t, err) - otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) otherClient := codersdk.NewExperimentalClient(otherClientRaw) _, err = otherClient.GetChat(ctx, createdChat.ID) requireSDKError(t, err, http.StatusNotFound) @@ -10355,7 +10315,7 @@ func TestGetChatDiffContents(t *testing.T) { }) require.NoError(t, err) - otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) otherClient := codersdk.NewExperimentalClient(otherClientRaw) _, err = otherClient.GetChatDiffContents(ctx, createdChat.ID) requireSDKError(t, err, http.StatusNotFound) @@ -10538,44 +10498,6 @@ func TestPromoteChatQueuedMessage(t *testing.T) { require.Contains(t, sdkErr.Detail, "invalid syntax") }) - t.Run("MemberWithoutAgentsAccess", func(t *testing.T) { - t.Parallel() - - ctx := testutil.Context(t, testutil.WaitLong) - client, db := newChatClientWithDatabase(t) - firstUser := coderdtest.CreateFirstUser(t, client.Client) - modelConfig := createChatModelConfig(t, client) - - // Create a member without agents-access. Without - // agents-access the member has no ResourceChat - // permissions, so the ChatParam middleware returns 404 - // before the handler can check agents-access. - memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) - memberClient := codersdk.NewExperimentalClient(memberClientRaw) - chat := dbgen.Chat(t, db, database.Chat{ - OrganizationID: firstUser.OrganizationID, - OwnerID: member.ID, - LastModelConfigID: modelConfig.ID, - Title: "promote queued no agents access", - }) - - queuedContent, err := json.Marshal([]codersdk.ChatMessagePart{ - codersdk.ChatMessageText("queued message no agents access"), - }) - require.NoError(t, err) - queuedMessage := insertTestChatQueuedMessage(ctx, t, db, chat.ID, queuedContent, chat.LastModelConfigID) - - promoteRes, err := memberClient.Request( - ctx, - http.MethodPost, - fmt.Sprintf("/api/experimental/chats/%s/queue/%d/promote", chat.ID, queuedMessage.ID), - nil, - ) - require.NoError(t, err) - defer promoteRes.Body.Close() - require.Equal(t, http.StatusNotFound, promoteRes.StatusCode) - }) - t.Run("ArchivedChat", func(t *testing.T) { t.Parallel() @@ -11068,20 +10990,20 @@ This arrived as octet-stream. requireSDKError(t, err, http.StatusUnauthorized) }) - t.Run("MemberWithoutAgentsAccess", func(t *testing.T) { + t.Run("MemberCanUploadChatFile", func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) client := newChatClient(t) firstUser := coderdtest.CreateFirstUser(t, client.Client) - // Member without agents-access should be denied. memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) data := append([]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}, make([]byte, 64)...) - _, err := memberClient.UploadChatFile(ctx, firstUser.OrganizationID, "image/png", "test.png", bytes.NewReader(data)) - requireSDKError(t, err, http.StatusForbidden) + uploaded, err := memberClient.UploadChatFile(ctx, firstUser.OrganizationID, "image/png", "test.png", bytes.NewReader(data)) + require.NoError(t, err) + require.NotEqual(t, uuid.Nil, uploaded.ID) }) } @@ -11265,7 +11187,7 @@ func TestGetChatFile(t *testing.T) { uploaded, err := client.UploadChatFile(ctx, firstUser.OrganizationID, "image/png", "test.png", bytes.NewReader(data)) require.NoError(t, err) - otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) otherClient := codersdk.NewExperimentalClient(otherClientRaw) _, _, err = otherClient.GetChatFile(ctx, uploaded.ID) requireSDKError(t, err, http.StatusNotFound) @@ -11629,9 +11551,7 @@ func TestGetChatCost(t *testing.T) { client, db := newChatClientWithDatabase(t) firstUser := coderdtest.CreateFirstUser(t, client.Client) - // agents-access is what grants ResourceChat; plain members cannot - // create or read chats at all, so they never reach this endpoint. - memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) modelConfig := createChatModelConfig(t, client) everyoneGroup := uuid.NullUUID{UUID: firstUser.OrganizationID, Valid: true} @@ -11797,21 +11717,13 @@ func TestWatchChatGitAuthz(t *testing.T) { }) require.NoError(t, err) - // Demote adminClient via the second owner. template-admin grants - // workspace:read (site) but not workspace:ssh or - // workspace:application_connect; agents-access preserves - // chat:create|read|update on chats the user owns, so the - // demoted user still passes ExtractChatParam for their own chat. + // Demote adminClient to template-admin. Membership still allows reading this + // chat, so ExtractChatParam passes before workspace authorization fails. _, err = secondAdminClient.UpdateUserRoles(ctx, firstUser.UserID.String(), codersdk.UpdateRoles{ Roles: []string{rbac.RoleTemplateAdmin().String()}, }) require.NoError(t, err) - _, err = secondAdminClient.UpdateOrganizationMemberRoles(ctx, firstUser.OrganizationID, firstUser.UserID.String(), codersdk.UpdateRoles{ - Roles: []string{rbac.RoleAgentsAccess()}, - }) - require.NoError(t, err) - res, err := adminClient.Request( ctx, http.MethodGet, @@ -13519,7 +13431,6 @@ func TestCreateChatPersonalModelOverrideRoot(t *testing.T) { t, adminClient.Client, firstUser.OrganizationID, - rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID), ) memberClient := codersdk.NewExperimentalClient(memberClientRaw) @@ -14007,7 +13918,7 @@ func TestChatDebugRuns(t *testing.T) { firstUser := coderdtest.CreateFirstUser(t, client.Client) modelConfig := createChatModelConfig(t, client) - memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) chat := dbgen.Chat(t, db, database.Chat{ @@ -14119,7 +14030,7 @@ func TestChatDebugRuns(t *testing.T) { seedChatDebugRun(ctx, t, db, chat.ID, time.Now().UTC()) - otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID)) + otherClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) otherClient := codersdk.NewExperimentalClient(otherClientRaw) _, err := otherClient.GetChatDebugRuns(ctx, chat.ID) @@ -15497,7 +15408,6 @@ func TestSubmitToolResults(t *testing.T) { // to user A's chat. otherClientRaw, _ := coderdtest.CreateAnotherUser( t, client.Client, user.OrganizationID, - rbac.ScopedRoleAgentsAccess(user.OrganizationID), ) otherClient := codersdk.NewExperimentalClient(otherClientRaw) @@ -15509,7 +15419,7 @@ func TestSubmitToolResults(t *testing.T) { requireSDKError(t, err, http.StatusNotFound) }) - t.Run("MemberWithoutAgentsAccess", func(t *testing.T) { + t.Run("MemberCanSubmitToolResults", func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) @@ -15517,24 +15427,20 @@ func TestSubmitToolResults(t *testing.T) { firstUser := coderdtest.CreateFirstUser(t, client.Client) modelConfig := createChatModelConfig(t, client) - // Create a member without agents-access. Without - // agents-access the member has no ResourceChat - // permissions, so the ChatParam middleware returns 404 - // before the handler can check agents-access. memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) const toolName = "my_dynamic_tool" - toolCallIDs := []string{"call_noaccess"} + toolCallIDs := []string{"call_member"} chat := setupRequiresAction(ctx, t, db, member.ID, firstUser.OrganizationID, modelConfig.ID, toolName, toolCallIDs) err := memberClient.SubmitToolResults(ctx, chat.ID, codersdk.SubmitToolResultsRequest{ Results: []codersdk.ToolResult{ - {ToolCallID: "call_noaccess", Output: json.RawMessage(`"should fail"`)}, + {ToolCallID: "call_member", Output: json.RawMessage(`"result"`)}, }, }) - requireSDKError(t, err, http.StatusNotFound) + require.NoError(t, err) }) t.Run("ArchivedChat", func(t *testing.T) { @@ -16126,7 +16032,6 @@ func TestChatReadOnlySharedWriteHandlers(t *testing.T) { t, ownerClient.Client, owner.OrganizationID, - rbac.ScopedRoleAgentsAccess(owner.OrganizationID), ) sharedClient = codersdk.NewExperimentalClient(sharedRaw) @@ -16367,7 +16272,6 @@ func TestChatOwnerOnlyWriteHandlers(t *testing.T) { ownerClient.Client, firstUser.OrganizationID, rbac.ScopedRoleOrgAdmin(firstUser.OrganizationID), - rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID), ) adminClient = codersdk.NewExperimentalClient(orgAdminRaw) return ownerClient, adminClient, chat, db diff --git a/coderd/rbac/roles.go b/coderd/rbac/roles.go index fdb462b7eb615..819878ebf31a9 100644 --- a/coderd/rbac/roles.go +++ b/coderd/rbac/roles.go @@ -22,7 +22,6 @@ const ( templateAdmin string = "template-admin" userAdmin string = "user-admin" auditor string = "auditor" - agentsAccess string = "agents-access" // customSiteRole is a placeholder for all custom site roles. // This is used for what roles can assign other roles. // TODO: Make this more dynamic to allow other roles to grant. @@ -143,7 +142,6 @@ func RoleTemplateAdmin() RoleIdentifier { return RoleIdentifier{Name: templateAd func RoleUserAdmin() RoleIdentifier { return RoleIdentifier{Name: userAdmin} } func RoleMember() RoleIdentifier { return RoleIdentifier{Name: member} } func RoleAuditor() RoleIdentifier { return RoleIdentifier{Name: auditor} } -func RoleAgentsAccess() string { return agentsAccess } func RoleOrgAdmin() string { return orgAdmin @@ -203,10 +201,6 @@ func ScopedRoleOrgWorkspaceCreationBan(organizationID uuid.UUID) RoleIdentifier return RoleIdentifier{Name: RoleOrgWorkspaceCreationBan(), OrganizationID: organizationID} } -func ScopedRoleAgentsAccess(organizationID uuid.UUID) RoleIdentifier { - return RoleIdentifier{Name: RoleAgentsAccess(), OrganizationID: organizationID} -} - func ScopedRoleOrgWorkspaceAccess(organizationID uuid.UUID) RoleIdentifier { return RoleIdentifier{Name: RoleOrgWorkspaceAccess(), OrganizationID: organizationID} } @@ -727,29 +721,6 @@ func ReloadBuiltinRoles(opts *RoleOptions) { }, } }, - // ActionDelete is intentionally excluded because hard-deletion goes through - // ResourceSystem in dbpurge. - agentsAccess: func(organizationID uuid.UUID) Role { - return Role{ - Identifier: RoleIdentifier{Name: agentsAccess, OrganizationID: organizationID}, - DisplayName: "Coder Agents User", - Site: []Permission{}, - User: []Permission{}, - ByOrgID: map[string]OrgPermissions{ - organizationID.String(): { - Org: []Permission{}, - Member: Permissions(map[string][]policy.Action{ - ResourceChat.Type: { - policy.ActionCreate, - policy.ActionRead, - policy.ActionShare, - policy.ActionUpdate, - }, - }), - }, - }, - } - }, } builtInRoles.Store(&roles) @@ -776,7 +747,6 @@ var assignRoles = map[string]map[string]bool{ userAdmin: true, customSiteRole: true, customOrganizationRole: true, - agentsAccess: true, }, owner: { owner: true, @@ -793,13 +763,11 @@ var assignRoles = map[string]map[string]bool{ userAdmin: true, customSiteRole: true, customOrganizationRole: true, - agentsAccess: true, }, userAdmin: { member: true, orgMember: true, orgWorkspaceAccess: true, - agentsAccess: true, }, orgAdmin: { orgAdmin: true, @@ -810,12 +778,10 @@ var assignRoles = map[string]map[string]bool{ orgWorkspaceCreationBan: true, orgWorkspaceAccess: true, customOrganizationRole: true, - agentsAccess: true, }, orgUserAdmin: { orgMember: true, orgWorkspaceAccess: true, - agentsAccess: true, }, } @@ -1186,8 +1152,6 @@ func OrgMemberPermissions(org OrgSettings) OrgRolePermissions { }) } - // Chat access requires the agents-access role and is intentionally - // not granted in the floor. memberPerms := Permissions(map[string][]policy.Action{ // Read-self org-member record. ResourceOrganizationMember.Type: {policy.ActionRead}, @@ -1209,6 +1173,14 @@ func OrgMemberPermissions(org OrgSettings) OrgRolePermissions { ResourceNotificationMessage.Type: {policy.ActionRead, policy.ActionUpdate}, ResourceNotificationPreference.Type: ResourceNotificationPreference.AvailableActions(), ResourceInboxNotification.Type: ResourceInboxNotification.AvailableActions(), + + // Hard deletion is authorized through ResourceSystem in dbpurge. + ResourceChat.Type: { + policy.ActionCreate, + policy.ActionRead, + policy.ActionShare, + policy.ActionUpdate, + }, }) if org.ShareableWorkspaceOwners != ShareableWorkspaceOwnersEveryone { @@ -1256,6 +1228,7 @@ func OrgServiceAccountPermissions(org OrgSettings) OrgRolePermissions { }) } + // Chat permissions are intentionally omitted for service accounts. memberPerms := Permissions(map[string][]policy.Action{ // Read-self org-member record. ResourceOrganizationMember.Type: {policy.ActionRead}, @@ -1266,8 +1239,7 @@ func OrgServiceAccountPermissions(org OrgSettings) OrgRolePermissions { // Service accounts can create and update AI Bridge interceptions // they initiate (dbauthz layer sets WithOwner(InitiatorID)) but - // cannot read them back. Chat access requires the agents-access - // role and is intentionally not granted here. + // cannot read them back. ResourceAibridgeInterception.Type: {policy.ActionCreate, policy.ActionUpdate}, // Own session tokens and workspace agent auth keys. diff --git a/coderd/rbac/roles_test.go b/coderd/rbac/roles_test.go index c5c484b01bcbf..89a7a94b313c0 100644 --- a/coderd/rbac/roles_test.go +++ b/coderd/rbac/roles_test.go @@ -126,18 +126,26 @@ func TestChatSharingPermissions(t *testing.T) { userID := uuid.NewString() resource := rbac.ResourceChat.WithID(uuid.New()).InOrg(orgID).WithOwner(userID) - authorizeAgentsAccessUser := func(t *testing.T) error { + authorizeOrgMember := func(t *testing.T) error { t.Helper() memberRole, err := rbac.RoleByName(rbac.RoleMember()) require.NoError(t, err) - agentsRole, err := rbac.RoleByName(rbac.ScopedRoleAgentsAccess(orgID)) - require.NoError(t, err) + perms := rbac.OrgMemberPermissions(rbac.OrgSettings{}) + orgMemberRole := rbac.Role{ + Identifier: rbac.ScopedRoleOrgMember(orgID), + ByOrgID: map[string]rbac.OrgPermissions{ + orgID.String(): { + Org: perms.Org, + Member: perms.Member, + }, + }, + } auth := rbac.NewStrictAuthorizer(prometheus.NewRegistry()) return auth.Authorize(context.Background(), rbac.Subject{ ID: userID, - Roles: rbac.Roles{memberRole, agentsRole}, + Roles: rbac.Roles{memberRole, orgMemberRole}, Scope: rbac.ScopeAll, }, policy.ActionShare, resource) } @@ -149,7 +157,7 @@ func TestChatSharingPermissions(t *testing.T) { memberRole, err := rbac.RoleByName(rbac.RoleMember()) require.NoError(t, err) assert.False(t, permissionGranted(memberRole.Site, target)) - require.NoError(t, authorizeAgentsAccessUser(t)) + require.NoError(t, authorizeOrgMember(t)) }) t.Run("Disabled", func(t *testing.T) { @@ -162,7 +170,7 @@ func TestChatSharingPermissions(t *testing.T) { require.NoError(t, err) assert.True(t, permissionGranted(memberRole.Site, target)) - err = authorizeAgentsAccessUser(t) + err = authorizeOrgMember(t) require.ErrorAs(t, err, &rbac.UnauthorizedError{}) }) } @@ -226,10 +234,12 @@ func TestMemberRolesExcludeWorkspacePerms(t *testing.T) { member := rbac.OrgMemberPermissions(orgSettings).Member require.False(t, hasResource(member, rbac.ResourceWorkspace.Type), "organization-member must not grant workspace permissions") require.True(t, hasResource(member, rbac.ResourceOrganizationMember.Type), "organization-member should grant read-self") + require.True(t, hasResource(member, rbac.ResourceChat.Type), "organization-member should grant chat access") sa := rbac.OrgServiceAccountPermissions(orgSettings).Member require.False(t, hasResource(sa, rbac.ResourceWorkspace.Type), "organization-service-account must not grant workspace permissions") require.True(t, hasResource(sa, rbac.ResourceOrganizationMember.Type), "organization-service-account should grant read-self") + require.False(t, hasResource(sa, rbac.ResourceChat.Type), "organization-service-account must not grant chat access") // The registered organization-workspace-access role is the grant // path for workspace permissions. @@ -288,21 +298,6 @@ func TestRolePermissions(t *testing.T) { orgUserAdmin := authSubject{Name: "org_user_admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgUserAdmin(orgID)}, Scope: rbac.ScopeAll}.WithCachedASTValue()} orgTemplateAdmin := authSubject{Name: "org_template_admin", Actor: rbac.Subject{ID: userAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgTemplateAdmin(orgID)}, Scope: rbac.ScopeAll}.WithCachedASTValue()} orgAdminBanWorkspace := authSubject{Name: "org_admin_workspace_ban", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgAdmin(orgID), rbac.ScopedRoleOrgWorkspaceCreationBan(orgID)}, Scope: rbac.ScopeAll}.WithCachedASTValue()} - agentsAccessUser := func() authSubject { - memberRole, err := rbac.RoleByName(rbac.RoleMember()) - require.NoError(t, err) - agentsRole, err := rbac.RoleByName(rbac.ScopedRoleAgentsAccess(orgID)) - require.NoError(t, err) - return authSubject{ - Name: "agents_access", - Actor: rbac.Subject{ - ID: currentUser.String(), - Roles: rbac.Roles{memberRole, agentsRole}, - Scope: rbac.ScopeAll, - }.WithCachedASTValue(), - } - }() - orgWorkspaceAccessUser := func() authSubject { memberRole, err := rbac.RoleByName(rbac.RoleMember()) require.NoError(t, err) @@ -357,7 +352,7 @@ func TestRolePermissions(t *testing.T) { // requiredSubjects are required to be asserted in each test case. This is // to make sure one is not forgotten. requiredSubjects := []authSubject{ - memberMe, owner, agentsAccessUser, orgWorkspaceAccessUser, + memberMe, owner, orgWorkspaceAccessUser, orgAdmin, otherOrgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin, templateAdmin, userAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin, } @@ -380,7 +375,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionRead}, Resource: rbac.ResourceUserObject(currentUser), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {owner, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgUserAdmin, otherOrgAdmin, otherOrgUserAdmin, orgAdmin, orgWorkspaceAccessUser}, + true: {owner, memberMe, templateAdmin, userAdmin, orgUserAdmin, otherOrgAdmin, otherOrgUserAdmin, orgAdmin, orgWorkspaceAccessUser}, false: { orgTemplateAdmin, orgAuditor, otherOrgAuditor, otherOrgTemplateAdmin, @@ -393,7 +388,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceUser, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, userAdmin}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, orgWorkspaceAccessUser}, }, }, { @@ -403,7 +398,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin, orgAdminBanWorkspace, orgWorkspaceAccessUser}, - false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, orgAuditor, orgUserAdmin}, + false: {setOtherOrg, memberMe, userAdmin, orgAuditor, orgUserAdmin}, }, }, { @@ -413,7 +408,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, orgAdminBanWorkspace, orgWorkspaceAccessUser}, - false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor}, + false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor}, }, }, { @@ -423,7 +418,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, orgWorkspaceAccessUser}, - false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgAdminBanWorkspace}, + false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgAdminBanWorkspace}, }, }, { @@ -433,7 +428,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspace.InOrg(orgID).WithOwner(policy.WildcardSymbol), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin}, - false: {setOtherOrg, orgUserAdmin, orgAuditor, memberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, orgUserAdmin, orgAuditor, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgWorkspaceAccessUser}, }, }, { @@ -443,7 +438,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgWorkspaceAccessUser}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin}, }, }, { @@ -453,7 +448,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgWorkspaceAccessUser}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin}, }, }, { @@ -462,7 +457,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, orgWorkspaceAccessUser}, - false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgAdminBanWorkspace}, + false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgAdminBanWorkspace}, }, }, { @@ -471,7 +466,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, orgAdminBanWorkspace, orgWorkspaceAccessUser}, - false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor}, + false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor}, }, }, { @@ -484,7 +479,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, orgAdminBanWorkspace, orgWorkspaceAccessUser}, false: { - memberMe, agentsAccessUser, setOtherOrg, + memberMe, setOtherOrg, templateAdmin, userAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, }, @@ -501,8 +496,7 @@ func TestRolePermissions(t *testing.T) { true: {}, false: { orgAdmin, owner, setOtherOrg, - userAdmin, memberMe, agentsAccessUser, - templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, + userAdmin, memberMe, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgAdminBanWorkspace, orgWorkspaceAccessUser, }, @@ -514,7 +508,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceTemplate.WithID(templateID).InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin}, - false: {setOtherOrg, orgUserAdmin, orgAuditor, memberMe, agentsAccessUser, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, orgUserAdmin, orgAuditor, memberMe, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -523,7 +517,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceTemplate.InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAuditor, orgAdmin, templateAdmin, orgTemplateAdmin}, - false: {setOtherOrg, orgUserAdmin, memberMe, agentsAccessUser, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, orgUserAdmin, memberMe, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -534,7 +528,7 @@ func TestRolePermissions(t *testing.T) { }), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin}, - false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, agentsAccessUser, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -545,7 +539,7 @@ func TestRolePermissions(t *testing.T) { true: {owner, templateAdmin}, // Org template admins can only read org scoped files. // File scope is currently not org scoped :cry: - false: {setOtherOrg, orgTemplateAdmin, orgAdmin, memberMe, agentsAccessUser, userAdmin, orgAuditor, orgUserAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, orgTemplateAdmin, orgAdmin, memberMe, userAdmin, orgAuditor, orgUserAdmin, orgWorkspaceAccessUser}, }, }, { @@ -553,7 +547,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionCreate, policy.ActionRead}, Resource: rbac.ResourceFile.WithID(fileID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {owner, memberMe, agentsAccessUser, templateAdmin, orgWorkspaceAccessUser}, + true: {owner, memberMe, templateAdmin, orgWorkspaceAccessUser}, false: {setOtherOrg, setOrgNotMe, userAdmin}, }, }, @@ -563,7 +557,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceOrganization, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -572,7 +566,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceOrganization.WithID(orgID).InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin}, - false: {setOtherOrg, orgTemplateAdmin, orgUserAdmin, orgAuditor, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, orgTemplateAdmin, orgUserAdmin, orgAuditor, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -581,7 +575,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceOrganization.WithID(orgID).InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin, auditor, orgAuditor, userAdmin, orgUserAdmin}, - false: {setOtherOrg, memberMe, agentsAccessUser, orgWorkspaceAccessUser}, + false: {setOtherOrg, memberMe, orgWorkspaceAccessUser}, }, }, { @@ -590,7 +584,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceAssignOrgRole, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, userAdmin, memberMe, agentsAccessUser, templateAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, userAdmin, memberMe, templateAdmin, orgWorkspaceAccessUser}, }, }, { @@ -599,7 +593,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceAssignRole, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, userAdmin}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, orgWorkspaceAccessUser}, }, }, { @@ -607,7 +601,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionRead}, Resource: rbac.ResourceAssignRole, AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {setOtherOrg, setOrgNotMe, owner, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + true: {setOtherOrg, setOrgNotMe, owner, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, false: {}, }, }, @@ -617,7 +611,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceAssignOrgRole.InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, userAdmin, orgUserAdmin}, - false: {setOtherOrg, memberMe, agentsAccessUser, templateAdmin, orgTemplateAdmin, orgAuditor, orgWorkspaceAccessUser}, + false: {setOtherOrg, memberMe, templateAdmin, orgTemplateAdmin, orgAuditor, orgWorkspaceAccessUser}, }, }, { @@ -626,7 +620,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceAssignOrgRole.InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin}, - false: {setOtherOrg, orgUserAdmin, orgTemplateAdmin, orgAuditor, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, orgUserAdmin, orgTemplateAdmin, orgAuditor, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -635,7 +629,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceAssignOrgRole.InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, orgUserAdmin, userAdmin, templateAdmin}, - false: {setOtherOrg, memberMe, agentsAccessUser, orgAuditor, orgTemplateAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, memberMe, orgAuditor, orgTemplateAdmin, orgWorkspaceAccessUser}, }, }, { @@ -643,7 +637,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete, policy.ActionUpdate}, Resource: rbac.ResourceApiKey.WithID(apiKeyID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {owner, memberMe, agentsAccessUser, orgWorkspaceAccessUser}, + true: {owner, memberMe, orgWorkspaceAccessUser}, false: {setOtherOrg, setOrgNotMe, templateAdmin, userAdmin}, }, }, @@ -655,7 +649,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceInboxNotification.WithID(uuid.New()).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin}, - false: {setOtherOrg, orgUserAdmin, orgTemplateAdmin, orgAuditor, templateAdmin, userAdmin, memberMe, agentsAccessUser, orgWorkspaceAccessUser}, + false: {setOtherOrg, orgUserAdmin, orgTemplateAdmin, orgAuditor, templateAdmin, userAdmin, memberMe, orgWorkspaceAccessUser}, }, }, { @@ -663,7 +657,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionReadPersonal, policy.ActionUpdatePersonal}, Resource: rbac.ResourceUserObject(currentUser), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {owner, memberMe, agentsAccessUser, userAdmin, orgWorkspaceAccessUser}, + true: {owner, memberMe, userAdmin, orgWorkspaceAccessUser}, false: {setOtherOrg, setOrgNotMe, templateAdmin}, }, }, @@ -673,7 +667,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceOrganizationMember.WithID(currentUser).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, userAdmin, orgUserAdmin}, - false: {setOtherOrg, orgTemplateAdmin, orgAuditor, memberMe, agentsAccessUser, templateAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, orgTemplateAdmin, orgAuditor, memberMe, templateAdmin, orgWorkspaceAccessUser}, }, }, { @@ -682,7 +676,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceOrganizationMember.WithID(currentUser).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAuditor, orgAdmin, userAdmin, templateAdmin, orgUserAdmin, orgTemplateAdmin}, - false: {memberMe, agentsAccessUser, setOtherOrg, orgWorkspaceAccessUser}, + false: {memberMe, setOtherOrg, orgWorkspaceAccessUser}, }, }, { @@ -694,7 +688,7 @@ func TestRolePermissions(t *testing.T) { }), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {owner, orgAdmin, templateAdmin, orgUserAdmin, orgTemplateAdmin, orgAuditor, agentsAccessUser, orgWorkspaceAccessUser}, + true: {owner, orgAdmin, templateAdmin, orgUserAdmin, orgTemplateAdmin, orgAuditor, orgWorkspaceAccessUser}, false: {setOtherOrg, memberMe, userAdmin}, }, }, @@ -708,7 +702,7 @@ func TestRolePermissions(t *testing.T) { }), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, userAdmin, orgUserAdmin}, - false: {setOtherOrg, memberMe, agentsAccessUser, templateAdmin, orgTemplateAdmin, orgAuditor, orgWorkspaceAccessUser}, + false: {setOtherOrg, memberMe, templateAdmin, orgTemplateAdmin, orgAuditor, orgWorkspaceAccessUser}, }, }, { @@ -721,7 +715,7 @@ func TestRolePermissions(t *testing.T) { }), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor}, - false: {setOtherOrg, memberMe, agentsAccessUser, orgWorkspaceAccessUser}, + false: {setOtherOrg, memberMe, orgWorkspaceAccessUser}, }, }, { @@ -730,7 +724,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceGroupMember.WithID(currentUser).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAuditor, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin}, - false: {setOtherOrg, memberMe, agentsAccessUser, orgWorkspaceAccessUser}, + false: {setOtherOrg, memberMe, orgWorkspaceAccessUser}, }, }, { @@ -739,7 +733,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceGroupMember.WithID(adminID).InOrg(orgID).WithOwner(adminID.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAuditor, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin}, - false: {setOtherOrg, memberMe, agentsAccessUser, orgWorkspaceAccessUser}, + false: {setOtherOrg, memberMe, orgWorkspaceAccessUser}, }, }, { @@ -748,7 +742,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {orgAdmin, owner, templateAdmin, orgTemplateAdmin, orgWorkspaceAccessUser}, - false: {setOtherOrg, userAdmin, memberMe, agentsAccessUser, orgUserAdmin, orgAuditor}, + false: {setOtherOrg, userAdmin, memberMe, orgUserAdmin, orgAuditor}, }, }, { @@ -757,7 +751,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {orgAdmin, owner, orgWorkspaceAccessUser}, - false: {setOtherOrg, userAdmin, memberMe, agentsAccessUser, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor}, + false: {setOtherOrg, userAdmin, memberMe, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor}, }, }, { @@ -766,7 +760,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, userAdmin, owner, templateAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, userAdmin, owner, templateAdmin, orgWorkspaceAccessUser}, }, }, { @@ -775,7 +769,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspace.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, orgWorkspaceAccessUser}, - false: {setOtherOrg, userAdmin, templateAdmin, memberMe, agentsAccessUser, orgTemplateAdmin, orgUserAdmin, orgAuditor}, + false: {setOtherOrg, userAdmin, templateAdmin, memberMe, orgTemplateAdmin, orgUserAdmin, orgAuditor}, }, }, { @@ -784,7 +778,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourcePrebuiltWorkspace.WithID(uuid.New()).InOrg(orgID).WithOwner(database.PrebuildsSystemUserID.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin}, - false: {setOtherOrg, userAdmin, memberMe, agentsAccessUser, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser}, + false: {setOtherOrg, userAdmin, memberMe, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser}, }, }, { @@ -793,7 +787,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceTask.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, orgWorkspaceAccessUser}, - false: {setOtherOrg, userAdmin, templateAdmin, memberMe, agentsAccessUser, orgTemplateAdmin, orgUserAdmin, orgAuditor}, + false: {setOtherOrg, userAdmin, templateAdmin, memberMe, orgTemplateAdmin, orgUserAdmin, orgAuditor}, }, }, // Some admin style resources @@ -803,7 +797,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceLicense, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -812,7 +806,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceDeploymentStats, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -821,7 +815,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceDeploymentConfig, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -830,7 +824,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceDebugInfo, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -839,7 +833,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceReplicas, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -848,7 +842,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceTailnetCoordinator, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -857,7 +851,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceAuditLog, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -866,7 +860,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceProvisionerDaemon.InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, templateAdmin, orgAdmin, orgTemplateAdmin}, - false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, agentsAccessUser, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -875,7 +869,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceProvisionerDaemon.InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, templateAdmin, orgAdmin, orgTemplateAdmin}, - false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, orgAuditor, orgUserAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, memberMe, userAdmin, orgAuditor, orgUserAdmin, orgWorkspaceAccessUser}, }, }, { @@ -884,7 +878,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceProvisionerDaemon.WithOwner(currentUser.String()).InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, templateAdmin, orgTemplateAdmin, orgAdmin, orgWorkspaceAccessUser}, - false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, orgUserAdmin, orgAuditor}, + false: {setOtherOrg, memberMe, userAdmin, orgUserAdmin, orgAuditor}, }, }, { @@ -893,7 +887,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceProvisionerDaemon.WithOwner(currentUser.String()).InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, templateAdmin, orgTemplateAdmin, orgAdmin}, - false: {orgWorkspaceAccessUser, setOtherOrg, memberMe, agentsAccessUser, userAdmin, orgUserAdmin, orgAuditor}, + false: {orgWorkspaceAccessUser, setOtherOrg, memberMe, userAdmin, orgUserAdmin, orgAuditor}, }, }, { @@ -902,7 +896,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceProvisionerJobs.InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgTemplateAdmin, orgAdmin}, - false: {setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser}, + false: {setOtherOrg, memberMe, templateAdmin, userAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser}, }, }, { @@ -911,7 +905,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceSystem, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -920,7 +914,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceOauth2App, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -928,7 +922,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionRead}, Resource: rbac.ResourceOauth2App, AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {owner, setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + true: {owner, setOrgNotMe, setOtherOrg, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, false: {}, }, }, @@ -938,7 +932,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceOauth2AppSecret, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOrgNotMe, setOtherOrg, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -947,7 +941,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceOauth2AppCodeToken, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOrgNotMe, setOtherOrg, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -956,7 +950,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspaceProxy, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOrgNotMe, setOtherOrg, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -964,7 +958,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionRead}, Resource: rbac.ResourceWorkspaceProxy, AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {owner, setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + true: {owner, setOrgNotMe, setOtherOrg, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, false: {}, }, }, @@ -974,7 +968,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceWorkspaceBuildOrchestration.InOrg(orgID), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOrgNotMe, setOtherOrg, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -984,7 +978,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate}, Resource: rbac.ResourceNotificationPreference.WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {orgWorkspaceAccessUser, memberMe, agentsAccessUser, owner}, + true: {orgWorkspaceAccessUser, memberMe, owner}, false: { userAdmin, orgUserAdmin, templateAdmin, orgAuditor, orgTemplateAdmin, @@ -1001,7 +995,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, false: { - orgWorkspaceAccessUser, memberMe, agentsAccessUser, userAdmin, orgUserAdmin, templateAdmin, + orgWorkspaceAccessUser, memberMe, userAdmin, orgUserAdmin, templateAdmin, orgAuditor, orgTemplateAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin, orgAdmin, otherOrgAdmin, @@ -1015,8 +1009,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, false: { - memberMe, agentsAccessUser, - orgAdmin, otherOrgAdmin, + memberMe, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin, @@ -1034,7 +1027,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, false: { - orgWorkspaceAccessUser, memberMe, agentsAccessUser, templateAdmin, orgUserAdmin, userAdmin, + orgWorkspaceAccessUser, memberMe, templateAdmin, orgUserAdmin, userAdmin, orgAdmin, orgAuditor, orgTemplateAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin, otherOrgAdmin, @@ -1047,7 +1040,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete}, Resource: rbac.ResourceWebpushSubscription.WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {owner, memberMe, agentsAccessUser, orgWorkspaceAccessUser}, + true: {owner, memberMe, orgWorkspaceAccessUser}, false: {orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin}, }, }, @@ -1059,7 +1052,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, userAdmin, orgAdmin, otherOrgAdmin, orgUserAdmin, otherOrgUserAdmin}, false: { - memberMe, agentsAccessUser, templateAdmin, + memberMe, templateAdmin, orgTemplateAdmin, orgAuditor, otherOrgAuditor, otherOrgTemplateAdmin, orgWorkspaceAccessUser, @@ -1073,8 +1066,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, orgAdmin, otherOrgAdmin}, false: { - userAdmin, memberMe, agentsAccessUser, - orgAuditor, orgUserAdmin, + userAdmin, memberMe, orgAuditor, orgUserAdmin, otherOrgAuditor, otherOrgUserAdmin, orgWorkspaceAccessUser, }, @@ -1087,7 +1079,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin, otherOrgAdmin, orgWorkspaceAccessUser}, false: { - memberMe, agentsAccessUser, userAdmin, templateAdmin, + memberMe, userAdmin, templateAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin, }, @@ -1099,7 +1091,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceCryptoKey, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -1110,7 +1102,7 @@ func TestRolePermissions(t *testing.T) { true: {owner, orgAdmin, orgUserAdmin, userAdmin}, false: { otherOrgAdmin, - memberMe, agentsAccessUser, templateAdmin, + memberMe, templateAdmin, orgAuditor, orgTemplateAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin, orgWorkspaceAccessUser, @@ -1126,7 +1118,7 @@ func TestRolePermissions(t *testing.T) { false: { orgAdmin, orgUserAdmin, otherOrgAdmin, - memberMe, agentsAccessUser, templateAdmin, + memberMe, templateAdmin, orgAuditor, orgTemplateAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin, orgWorkspaceAccessUser, @@ -1140,8 +1132,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, false: { - memberMe, agentsAccessUser, - orgAdmin, otherOrgAdmin, + memberMe, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin, @@ -1156,8 +1147,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, false: { - memberMe, agentsAccessUser, - orgAdmin, otherOrgAdmin, + memberMe, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin, @@ -1171,7 +1161,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceConnectionLog, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, // Only the user themselves can access their own secrets — no one else. @@ -1180,7 +1170,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}, Resource: rbac.ResourceUserSecret.WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {memberMe, agentsAccessUser, orgWorkspaceAccessUser}, + true: {memberMe, orgWorkspaceAccessUser}, false: { owner, orgAdmin, otherOrgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin, @@ -1195,7 +1185,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionRead, policy.ActionDelete}, Resource: rbac.ResourceUserSkill.WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {owner, memberMe, agentsAccessUser, orgWorkspaceAccessUser}, + true: {owner, memberMe, orgWorkspaceAccessUser}, false: { orgAdmin, otherOrgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin, @@ -1208,7 +1198,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate}, Resource: rbac.ResourceUserSkill.WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {memberMe, agentsAccessUser, orgWorkspaceAccessUser}, + true: {memberMe, orgWorkspaceAccessUser}, false: { owner, orgAdmin, otherOrgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin, @@ -1224,8 +1214,7 @@ func TestRolePermissions(t *testing.T) { true: {}, false: { owner, - memberMe, agentsAccessUser, - orgAdmin, otherOrgAdmin, + memberMe, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin, @@ -1239,7 +1228,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate}, Resource: rbac.ResourceAibridgeInterception.WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {orgWorkspaceAccessUser, owner, memberMe, agentsAccessUser}, + true: {orgWorkspaceAccessUser, owner, memberMe}, false: { orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, @@ -1256,8 +1245,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, auditor}, false: { - orgWorkspaceAccessUser, memberMe, agentsAccessUser, - orgAdmin, otherOrgAdmin, + orgWorkspaceAccessUser, memberMe, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin, @@ -1275,8 +1263,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, false: { - orgWorkspaceAccessUser, memberMe, agentsAccessUser, - orgAdmin, otherOrgAdmin, + orgWorkspaceAccessUser, memberMe, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin, @@ -1293,8 +1280,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, false: { - orgWorkspaceAccessUser, memberMe, agentsAccessUser, - orgAdmin, otherOrgAdmin, + orgWorkspaceAccessUser, memberMe, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin, @@ -1312,8 +1298,7 @@ func TestRolePermissions(t *testing.T) { true: {}, false: { owner, - orgWorkspaceAccessUser, memberMe, agentsAccessUser, - orgAdmin, otherOrgAdmin, + orgWorkspaceAccessUser, memberMe, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin, @@ -1325,7 +1310,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}, Resource: rbac.ResourceBoundaryUsage, AuthorizeMap: map[bool][]hasAuthSubjects{ - false: {owner, setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {owner, setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -1333,7 +1318,7 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionCreate, policy.ActionRead}, Resource: rbac.ResourceAiSeat, AuthorizeMap: map[bool][]hasAuthSubjects{ - false: {owner, setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {owner, setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { @@ -1342,18 +1327,17 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceAiModelPrice, AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner}, - false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser}, + false: {setOtherOrg, setOrgNotMe, memberMe, templateAdmin, userAdmin, orgWorkspaceAccessUser}, }, }, { - // Boundary logs: members can create logs they own (user-scoped). - // memberMe and agentsAccessUser have ID == currentUser, so they - // match the resource owner. Other subjects have different IDs. + // Both allowed subjects use currentUser as their ID, matching the + // resource owner. Name: "BoundaryLogCreate", Actions: []policy.Action{policy.ActionCreate}, Resource: rbac.ResourceBoundaryLog.WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {orgWorkspaceAccessUser, memberMe, agentsAccessUser}, + true: {orgWorkspaceAccessUser, memberMe}, false: { owner, orgAdmin, otherOrgAdmin, @@ -1373,8 +1357,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {}, false: { - orgWorkspaceAccessUser, owner, memberMe, agentsAccessUser, - orgAdmin, otherOrgAdmin, + orgWorkspaceAccessUser, owner, memberMe, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, auditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin, @@ -1390,8 +1373,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {}, false: { - orgWorkspaceAccessUser, owner, memberMe, agentsAccessUser, - orgAdmin, otherOrgAdmin, + orgWorkspaceAccessUser, owner, memberMe, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, auditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin, @@ -1406,8 +1388,7 @@ func TestRolePermissions(t *testing.T) { AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, auditor}, false: { - orgWorkspaceAccessUser, memberMe, agentsAccessUser, - orgAdmin, otherOrgAdmin, + orgWorkspaceAccessUser, memberMe, orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin, @@ -1419,8 +1400,8 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate}, Resource: rbac.ResourceChat.WithID(uuid.New()).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {owner, orgAdmin, agentsAccessUser}, - false: {setOtherOrg, memberMe, orgMemberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser}, + true: {owner, orgAdmin, orgMemberMe}, + false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser}, }, }, { @@ -1428,8 +1409,8 @@ func TestRolePermissions(t *testing.T) { Actions: []policy.Action{policy.ActionShare}, Resource: rbac.ResourceChat.WithID(uuid.New()).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ - true: {owner, orgAdmin, agentsAccessUser}, - false: {setOtherOrg, memberMe, orgMemberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser}, + true: {owner, orgAdmin, orgMemberMe}, + false: {setOtherOrg, memberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser}, }, }, { @@ -1438,7 +1419,7 @@ func TestRolePermissions(t *testing.T) { Resource: rbac.ResourceChat.WithID(uuid.New()).InOrg(orgID).WithOwner(currentUser.String()), AuthorizeMap: map[bool][]hasAuthSubjects{ true: {owner, orgAdmin}, - false: {setOtherOrg, memberMe, orgMemberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser}, + false: {setOtherOrg, memberMe, orgMemberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser}, }, }, } @@ -1596,7 +1577,6 @@ func TestListRoles(t *testing.T) { fmt.Sprintf("organization-template-admin:%s", orgID.String()), fmt.Sprintf("organization-workspace-creation-ban:%s", orgID.String()), fmt.Sprintf("organization-workspace-access:%s", orgID.String()), - fmt.Sprintf("agents-access:%s", orgID.String()), }, orgRoleNames) } diff --git a/codersdk/rbacroles.go b/codersdk/rbacroles.go index 71b82c6340d78..ddb66e2d8913c 100644 --- a/codersdk/rbacroles.go +++ b/codersdk/rbacroles.go @@ -7,7 +7,6 @@ const ( RoleTemplateAdmin string = "template-admin" RoleUserAdmin string = "user-admin" RoleAuditor string = "auditor" - RoleAgentsAccess string = "agents-access" RoleOrganizationAdmin string = "organization-admin" RoleOrganizationMember string = "organization-member" diff --git a/docs/ai-coder/agents/getting-started.md b/docs/ai-coder/agents/getting-started.md index 4579c72819a77..79804c52d3b33 100644 --- a/docs/ai-coder/agents/getting-started.md +++ b/docs/ai-coder/agents/getting-started.md @@ -17,11 +17,7 @@ Before you begin, confirm the following: [descriptive name and description](./platform-controls/template-optimization.md) for the agent to select when provisioning workspaces. - **Admin access** to the Coder deployment for configuring providers. -- **Coder Agents User role** assigned to each user who needs to interact with Coder Agents. - This role is granted **per organization**. Owners and organization admins can - assign it from **Admin settings** > **Organizations** > _[your organization]_ > - **Members**. See [Grant Coder Agents User](#step-2-grant-coder-agents-user) - below. +- **Organization membership** for each user who uses Coder Agents. ## Step 1: Configure an LLM provider and model @@ -47,65 +43,7 @@ Detailed instructions for each provider and model option are in the > Start with a single frontier model to validate your setup before adding > additional providers. -## Step 2: Grant Coder Agents User - -The **Coder Agents User** role controls which users can interact with Coder -Agents. The role is assigned **per organization**, so a user must be granted -it in each organization where they need access. Members do not have it by -default. - -Owners always have full access and do not need the role. Repeat the following -steps for each user who needs access in each organization. - -**Dashboard (individual):** - -1. Open **Admin settings** > **Organizations** in the Coder dashboard, then - select the organization where you want to grant access. -1. The **Members** tab opens by default. Find the user in the table. -1. Click the **Roles** cell for that user to open the role editor. -1. Toggle on **Coder Agents User** and save. - -> [!TIP] -> If your deployment has multiple organizations, repeat this for each -> organization where the user needs access. - -**CLI (bulk, per organization):** - -Granting the role via CLI is org-scoped. The `edit-roles` command **replaces** -the member's full set of org roles, so include every role you want them to -keep. To grant `agents-access` to a single user while preserving their -existing org roles: - -```sh -ORG="my-org" -USER="alice" -ROLES=$(coder organizations members list -O "$ORG" -o json \ - | jq -r --arg user "$USER" \ - '.[] | select(.username == $user) | [.roles[].name, "agents-access"] - | unique | join(" ")') -# shellcheck disable=SC2086 -coder organizations members edit-roles "$USER" -O "$ORG" $ROLES -``` - -To grant the role to every member of an organization while preserving their -existing roles: - -```sh -ORG="my-org" -coder organizations members list -O "$ORG" -o json \ - | jq -c '.[] | {user_id, roles: [.roles[].name]}' \ - | while read -r row; do - user_id=$(echo "$row" | jq -r '.user_id') - roles=$(echo "$row" | jq -r '(.roles + ["agents-access"]) | unique | join(" ")') - # shellcheck disable=SC2086 - coder organizations members edit-roles "$user_id" -O "$ORG" $roles - done -``` - -You can also set the organization with the `CODER_ORGANIZATION` environment -variable instead of `-O`. - -## Step 3: Start your first Coder Agent +## Step 2: Start your first Coder Agent 1. Go to the **Agents** page in the Coder dashboard. 1. Select a model from the dropdown (your default will be pre-selected). diff --git a/docs/reference/glossary.md b/docs/reference/glossary.md index a172d2dd9610d..9b45088372724 100644 --- a/docs/reference/glossary.md +++ b/docs/reference/glossary.md @@ -100,11 +100,6 @@ The agent loop runs in the control plane, and developers work through the dashbo Not to be confused with a [workspace agent](#workspace-agent) or the [`coder_agent`](#coder_agent) resource. Refer to [Coder Agents](../ai-coder/agents/index.md). -### Coder Agents User - -The per-organization role that a member needs to use [Coder Agents](#coder-agents). -Refer to [Coder Agents](../ai-coder/agents/index.md). - ### Coder CLI The single `coder` binary used for admin and user operations. diff --git a/enterprise/coderd/exp_chats_test.go b/enterprise/coderd/exp_chats_test.go index 364b82fc6826b..4a77359682578 100644 --- a/enterprise/coderd/exp_chats_test.go +++ b/enterprise/coderd/exp_chats_test.go @@ -1108,11 +1108,9 @@ func TestCreateChatNonDefaultOrg(t *testing.T) { // Create a second (non-default) org via the API. secondOrg := coderdenttest.CreateOrganization(t, client, coderdenttest.CreateOrganizationOptions{}) - // Create a member with agents-access in both orgs. memberClientRaw, member := coderdtest.CreateAnotherUser( t, client, firstUser.OrganizationID, - rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID), - rbac.ScopedRoleAgentsAccess(secondOrg.ID), + rbac.ScopedRoleOrgMember(secondOrg.ID), ) memberClient := codersdk.NewExperimentalClient(memberClientRaw) // Create a chat in the non-default org. @@ -1177,11 +1175,9 @@ func TestListChats_OrgAdminOnlySeesOwnChats(t *testing.T) { // Create a second (non-default) org. secondOrg := coderdenttest.CreateOrganization(t, client, coderdenttest.CreateOrganizationOptions{}) - // Create a member with agents-access in both orgs. memberClientRaw, _ := coderdtest.CreateAnotherUser( t, client, firstUser.OrganizationID, - rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID), - rbac.ScopedRoleAgentsAccess(secondOrg.ID), + rbac.ScopedRoleOrgMember(secondOrg.ID), ) memberExp := codersdk.NewExperimentalClient(memberClientRaw) // Member creates a chat in the second org. @@ -1197,10 +1193,9 @@ func TestListChats_OrgAdminOnlySeesOwnChats(t *testing.T) { require.NoError(t, err) require.Equal(t, secondOrg.ID, memberChat.OrganizationID) - // Create an org admin in the second org with agents access. adminClientRaw, _ := coderdtest.CreateAnotherUser( t, client, firstUser.OrganizationID, - rbac.ScopedRoleOrgAdmin(secondOrg.ID), rbac.ScopedRoleAgentsAccess(secondOrg.ID), + rbac.ScopedRoleOrgAdmin(secondOrg.ID), ) adminExp := codersdk.NewExperimentalClient(adminClientRaw) diff --git a/enterprise/coderd/roles_test.go b/enterprise/coderd/roles_test.go index e2cc4df5bb215..62fe743a561c0 100644 --- a/enterprise/coderd/roles_test.go +++ b/enterprise/coderd/roles_test.go @@ -506,7 +506,6 @@ func TestListRoles(t *testing.T) { {Name: codersdk.RoleOrganizationUserAdmin, OrganizationID: owner.OrganizationID}: false, {Name: codersdk.RoleOrganizationWorkspaceCreationBan, OrganizationID: owner.OrganizationID}: false, {Name: codersdk.RoleOrganizationWorkspaceAccess, OrganizationID: owner.OrganizationID}: false, - {Name: codersdk.RoleAgentsAccess, OrganizationID: owner.OrganizationID}: false, }), }, { @@ -541,7 +540,6 @@ func TestListRoles(t *testing.T) { {Name: codersdk.RoleOrganizationUserAdmin, OrganizationID: owner.OrganizationID}: true, {Name: codersdk.RoleOrganizationWorkspaceCreationBan, OrganizationID: owner.OrganizationID}: true, {Name: codersdk.RoleOrganizationWorkspaceAccess, OrganizationID: owner.OrganizationID}: true, - {Name: codersdk.RoleAgentsAccess, OrganizationID: owner.OrganizationID}: true, }), }, { @@ -576,7 +574,6 @@ func TestListRoles(t *testing.T) { {Name: codersdk.RoleOrganizationUserAdmin, OrganizationID: owner.OrganizationID}: true, {Name: codersdk.RoleOrganizationWorkspaceCreationBan, OrganizationID: owner.OrganizationID}: true, {Name: codersdk.RoleOrganizationWorkspaceAccess, OrganizationID: owner.OrganizationID}: true, - {Name: codersdk.RoleAgentsAccess, OrganizationID: owner.OrganizationID}: true, }), }, } diff --git a/site/site_test.go b/site/site_test.go index 3aceee175e38a..476ac29e5b263 100644 --- a/site/site_test.go +++ b/site/site_test.go @@ -248,61 +248,54 @@ func TestRenderPermissionsResolvesMe(t *testing.T) { }) require.NoError(t, err) - // GIVEN: a user with the agents-access role at the org level. org := dbgen.Organization(t, db, database.Organization{}) - userWithRole := dbgen.User(t, db, database.User{}) + orgMember := dbgen.User(t, db, database.User{}) dbgen.OrganizationMember(t, db, database.OrganizationMember{ OrganizationID: org.ID, - UserID: userWithRole.ID, - Roles: []string{rbac.RoleAgentsAccess()}, + UserID: orgMember.ID, }) - _, tokenWithRole := dbgen.APIKey(t, db, database.APIKey{ - UserID: userWithRole.ID, + _, orgMemberToken := dbgen.APIKey(t, db, database.APIKey{ + UserID: orgMember.ID, ExpiresAt: time.Now().Add(time.Hour), }) // WHEN: the user loads the page. r := httptest.NewRequest("GET", "/", nil) - r.Header.Set(codersdk.SessionTokenHeader, tokenWithRole) + r.Header.Set(codersdk.SessionTokenHeader, orgMemberToken) rw := httptest.NewRecorder() handler.ServeHTTP(rw, r) require.Equal(t, http.StatusOK, rw.Code) - // THEN: the SSR-rendered permissions include createChat = true - // because the agents-access role grants org-scoped chat create - // permission, and the any_org check picks it up. - var permsWithRole codersdk.AuthorizationResponse - err = json.Unmarshal([]byte(html.UnescapeString(rw.Body.String())), &permsWithRole) + // The any_org check finds chat permission through the user's membership. + var memberPerms codersdk.AuthorizationResponse + err = json.Unmarshal([]byte(html.UnescapeString(rw.Body.String())), &memberPerms) require.NoError(t, err) - assert.True(t, permsWithRole["createChat"], "user with agents-access role should have createChat = true") + assert.True(t, memberPerms["createChat"], "organization member should have createChat = true") // THEN: createWorkspace = true because the organization-member role // grants creating a workspace owned by the member, and owner_id "me" // resolves to the requesting user. - assert.True(t, permsWithRole["createWorkspace"], "org member should have createWorkspace = true") + assert.True(t, memberPerms["createWorkspace"], "org member should have createWorkspace = true") - // GIVEN: a user without the agents-access role. - userWithoutRole := dbgen.User(t, db, database.User{}) - _, tokenWithoutRole := dbgen.APIKey(t, db, database.APIKey{ - UserID: userWithoutRole.ID, + userWithoutMembership := dbgen.User(t, db, database.User{}) + _, tokenWithoutMembership := dbgen.APIKey(t, db, database.APIKey{ + UserID: userWithoutMembership.ID, ExpiresAt: time.Now().Add(time.Hour), }) // WHEN: the user loads the page. r = httptest.NewRequest("GET", "/", nil) - r.Header.Set(codersdk.SessionTokenHeader, tokenWithoutRole) + r.Header.Set(codersdk.SessionTokenHeader, tokenWithoutMembership) rw = httptest.NewRecorder() handler.ServeHTTP(rw, r) require.Equal(t, http.StatusOK, rw.Code) - // THEN: createChat = false because the member role does not - // grant chat permissions. - var permsWithoutRole codersdk.AuthorizationResponse - err = json.Unmarshal([]byte(html.UnescapeString(rw.Body.String())), &permsWithoutRole) + var permsWithoutMembership codersdk.AuthorizationResponse + err = json.Unmarshal([]byte(html.UnescapeString(rw.Body.String())), &permsWithoutMembership) require.NoError(t, err) - assert.False(t, permsWithoutRole["createChat"], "user without agents-access role should have createChat = false") + assert.False(t, permsWithoutMembership["createChat"], "user without an organization membership should have createChat = false") // THEN: createWorkspace = false because the user belongs to no // organization, so the any_org check has no memberships to satisfy it. - assert.False(t, permsWithoutRole["createWorkspace"], "user without an org membership should have createWorkspace = false") + assert.False(t, permsWithoutMembership["createWorkspace"], "user without an org membership should have createWorkspace = false") // GIVEN: an org member whose only membership carries the // workspace-creation ban role. diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index cfc4fc94650c5..cf4d785554201 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -8040,12 +8040,6 @@ export interface Role { readonly organization_member_permissions: readonly Permission[]; } -// From codersdk/rbacroles.go -/** - * Ideally these roles would be generated from the rbac/roles.go package. - */ -export const RoleAgentsAccess = "agents-access"; - // From codersdk/rbacroles.go /** * Ideally these roles would be generated from the rbac/roles.go package. diff --git a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx index dd88e6cac51a9..f420206ad7b88 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx @@ -106,7 +106,7 @@ export const ForSingleOrgOSSAdmin: Story = { }, }; -export const ForMember: Story = { +export const ForUserWithoutOrganization: Story = { args: { user: MockUserMember, adminPermissions: {}, @@ -114,7 +114,7 @@ export const ForMember: Story = { }, }; -export const ForMemberWithAgentsAccess: Story = { +export const ForMember: Story = { args: { user: MockUserMember, adminPermissions: {}, diff --git a/site/src/modules/roles/RoleSelector.stories.tsx b/site/src/modules/roles/RoleSelector.stories.tsx index 3f14e7fb0a9d9..417a8a846db82 100644 --- a/site/src/modules/roles/RoleSelector.stories.tsx +++ b/site/src/modules/roles/RoleSelector.stories.tsx @@ -2,7 +2,6 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { action } from "storybook/actions"; import { assignableRole, - MockAgentsAccessRole, MockAuditorRole, MockOrganizationAdminRole, MockOrganizationAuditorRole, @@ -80,7 +79,6 @@ const orgMemberRoles = [ assignableRole(MockOrganizationUserAdminRole, true), assignableRole(MockOrganizationTemplateAdminRole, true), assignableRole(MockOrganizationAuditorRole, true), - assignableRole(MockAgentsAccessRole, true), assignableRole(MockWorkspaceCreationBanRole, true), ]; @@ -94,7 +92,7 @@ export const WithAdditionalImpliedRoles: Story = { args: { availableRoles: orgMemberRoles, additionalImpliedRoles: [ - assignableRole(MockAgentsAccessRole, true), + assignableRole(MockWorkspaceCreationBanRole, true), assignableRole(MockOrganizationAuditorRole, true), ], }, @@ -105,7 +103,7 @@ const mixedBuiltInAndCustomRoles = [ assignableRole(MockOrganizationUserAdminRole, true), assignableRole(MockOrganizationTemplateAdminRole, true), { ...assignableRole(MockOrganizationAuditorRole, true), built_in: false }, - { ...assignableRole(MockAgentsAccessRole, true), built_in: false }, + { ...assignableRole(MockWorkspaceCreationBanRole, true), built_in: false }, ]; export const WithDisabledReason: Story = { diff --git a/site/src/modules/roles/index.ts b/site/src/modules/roles/index.ts index 222ecee8002c9..55246db38539f 100644 --- a/site/src/modules/roles/index.ts +++ b/site/src/modules/roles/index.ts @@ -10,7 +10,6 @@ export const roleDescriptions: Record = { "user-admin": "User admin can manage all users and groups.", "template-admin": "Template admin can manage all templates and workspaces.", auditor: "Auditor can access the audit logs.", - "agents-access": "Grants access to Coder Agents chat.", "organization-admin": "Organization admin can manage all resources within this organization.", "organization-user-admin": @@ -53,7 +52,6 @@ const roleNamesByAccessLevel: readonly string[] = [ "organization-template-admin", "auditor", "organization-auditor", - "agents-access", "member", "organization-member", ]; diff --git a/site/src/pages/AgentsPage/components/AgentCreateForm.stories.tsx b/site/src/pages/AgentsPage/components/AgentCreateForm.stories.tsx index 59ed37449314b..e3dc97f416d6f 100644 --- a/site/src/pages/AgentsPage/components/AgentCreateForm.stories.tsx +++ b/site/src/pages/AgentsPage/components/AgentCreateForm.stories.tsx @@ -879,7 +879,7 @@ export const HookDenied: Story = { }, }; -export const ForbiddenErrorWithRole: Story = { +export const ForbiddenErrorWithPermission: Story = { args: { ...defaultArgs, canCreateChat: true, @@ -887,14 +887,10 @@ export const ForbiddenErrorWithRole: Story = { }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); - // The friendly "role required" alert must NOT appear because the - // user has the agents-access role. await expect( canvas.queryByText("Permission required"), ).not.toBeInTheDocument(); - // The generic ErrorAlert should surface the real backend message. await expect(canvas.getByText("Forbidden.")).toBeInTheDocument(); - // The textbox should remain enabled since the user has the role. const textbox = canvas.getByRole("textbox"); await expect(textbox).not.toHaveAttribute("aria-disabled", "true"); }, @@ -939,7 +935,6 @@ export const RestrictedMultiOrganizationUser: Story = { MockDefaultOrganization, MockOrganization2, ]); - // Model agents-access: "me" supplies the owner for member-scoped chat:create. spyOn(API, "checkAuthorization").mockImplementation(async ({ checks }) => Object.fromEntries( Object.entries(checks).map(([id, check]) => [ @@ -1313,7 +1308,7 @@ export const EmptyPermittedSetPreservesStoredWorkspace: Story = { revocablePermissions[MockOrganization2.id] = false; await revocableQueryClient?.invalidateQueries(); - await canvas.findByText(/don't have permission/i); + await canvas.findByText(/You need access to an organization/i); revocablePermissions[MockOrganization2.id] = true; await revocableQueryClient?.invalidateQueries(); @@ -1462,7 +1457,7 @@ export const OrgChangeConfirmation: Story = { }, }; -export const ForbiddenNoAgentsRole: Story = { +export const ForbiddenNoOrganizationAccess: Story = { args: { ...defaultArgs, canCreateChat: false, @@ -1471,6 +1466,9 @@ export const ForbiddenNoAgentsRole: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); await expect(canvas.getByText("Permission required")).toBeInTheDocument(); + await expect( + canvas.getByText(/You need access to an organization/), + ).toBeInTheDocument(); await expect( canvas.getByRole("link", { name: /View Docs/ }), ).toBeInTheDocument(); @@ -1522,7 +1520,9 @@ export const PermittedOrgsResolvesToEmpty: Story = { const canvas = within(canvasElement); await waitFor( () => { - expect(canvas.getByText(/don't have permission/i)).toBeInTheDocument(); + expect( + canvas.getByText(/You need access to an organization/i), + ).toBeInTheDocument(); }, { timeout: 3000 }, ); @@ -1582,11 +1582,6 @@ export const PermittedOrgsResolvesToSubset: Story = { }, }; -/** - * Member-scoped roles like agents-access grant chat:create only on - * chats the user owns, so the per-org check must carry owner context - * for the picker to render. - */ export const MemberScopedPermissionsShowOrgPicker: Story = { parameters: { showOrganizations: true, diff --git a/site/src/pages/AgentsPage/components/AgentCreateForm.tsx b/site/src/pages/AgentsPage/components/AgentCreateForm.tsx index 5b3f382214524..ac1b1c5fd1a5c 100644 --- a/site/src/pages/AgentsPage/components/AgentCreateForm.tsx +++ b/site/src/pages/AgentsPage/components/AgentCreateForm.tsx @@ -285,8 +285,7 @@ export const AgentCreateForm: FC = ({ useState(null); const permittedOrgsQuery = useQuery({ ...permittedOrganizations({ - // agents-access grants chat:create only at member scope. "me" is - // replaced with the caller ID so that permission can match. + // "me" resolves to the caller ID for owner-scoped permissions. object: { resource_type: "chat", owner_id: "me" }, action: "create", }), diff --git a/site/src/pages/AgentsPage/components/ChatAccessDeniedAlert.tsx b/site/src/pages/AgentsPage/components/ChatAccessDeniedAlert.tsx index e1a580e4ffdbf..15b4f0b1df355 100644 --- a/site/src/pages/AgentsPage/components/ChatAccessDeniedAlert.tsx +++ b/site/src/pages/AgentsPage/components/ChatAccessDeniedAlert.tsx @@ -5,9 +5,7 @@ import { Link } from "#/components/Link/Link"; import { docs } from "#/utils/docs"; export const ChatAccessDeniedAlert: FC = () => { - const docsLink = docs( - "/ai-coder/agents/getting-started#step-3-grant-coder-agents-user", - ); + const docsLink = docs("/ai-coder/agents/getting-started"); return ( { > Permission required - You don't have permission to use Coder Agents. Contact your Coder - administrator for access. Refresh this page after access has been - granted.{" "} + You need access to an organization to use Coder Agents. Contact your + Coder administrator for access, then refresh this page.{" "} View Docs diff --git a/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPageView.stories.tsx index b9b16b97d7d7b..3edbbc2b6ba8d 100644 --- a/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPageView.stories.tsx @@ -33,17 +33,6 @@ const mockOrgRoles: AssignableRoles[] = [ assignable: true, built_in: true, }, - { - name: "agents-access", - display_name: "Agents Access", - organization_id: MockOrganization.id, - site_permissions: [], - organization_permissions: [], - organization_member_permissions: [], - user_permissions: [], - assignable: true, - built_in: true, - }, ]; const meta: Meta = { diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 65a1ff3bbea75..10ec703d11a4b 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -377,16 +377,6 @@ export const MockOrganizationAuditorRole: TypesGen.AssignableRoles = { organization_member_permissions: [], }; -export const MockAgentsAccessRole: TypesGen.Role = { - name: "agents-access", - display_name: "Coder Agents User", - site_permissions: [], - user_permissions: [], - organization_id: MockOrganization.id, - organization_permissions: [], - organization_member_permissions: [], -}; - export const MockRoleWithOrgPermissions: TypesGen.AssignableRoles = { name: "my-role-1", display_name: "My Role 1",