Skip to content

Commit 76b949c

Browse files
committed
coderd/rbac: update chat rbac
1 parent 7d24eed commit 76b949c

File tree

7 files changed

+143
-18
lines changed

7 files changed

+143
-18
lines changed

coderd/database/dbauthz/dbauthz.go

+17-18
Original file line numberDiff line numberDiff line change
@@ -1270,10 +1270,7 @@ func (q *querier) DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, u
12701270
}
12711271

12721272
func (q *querier) DeleteChat(ctx context.Context, id uuid.UUID) error {
1273-
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceChat.WithID(id)); err != nil {
1274-
return err
1275-
}
1276-
return q.db.DeleteChat(ctx, id)
1273+
return deleteQ(q.log, q.auth, q.db.GetChatByID, q.db.DeleteChat)(ctx, id)
12771274
}
12781275

12791276
func (q *querier) DeleteCoordinator(ctx context.Context, id uuid.UUID) error {
@@ -1694,24 +1691,19 @@ func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUI
16941691
}
16951692

16961693
func (q *querier) GetChatByID(ctx context.Context, id uuid.UUID) (database.Chat, error) {
1697-
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceChat.WithID(id)); err != nil {
1698-
return database.Chat{}, err
1699-
}
1700-
return q.db.GetChatByID(ctx, id)
1694+
return fetch(q.log, q.auth, q.db.GetChatByID)(ctx, id)
17011695
}
17021696

17031697
func (q *querier) GetChatMessagesByChatID(ctx context.Context, chatID uuid.UUID) ([]database.ChatMessage, error) {
1704-
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceChat.WithID(chatID)); err != nil {
1698+
c, err := q.GetChatByID(ctx, chatID)
1699+
if err != nil {
17051700
return nil, err
17061701
}
1707-
return q.db.GetChatMessagesByChatID(ctx, chatID)
1702+
return q.db.GetChatMessagesByChatID(ctx, c.ID)
17081703
}
17091704

17101705
func (q *querier) GetChatsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]database.Chat, error) {
1711-
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceChat); err != nil {
1712-
return nil, err
1713-
}
1714-
return q.db.GetChatsByOwnerID(ctx, ownerID)
1706+
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetChatsByOwnerID)(ctx, ownerID)
17151707
}
17161708

17171709
func (q *querier) GetCoordinatorResumeTokenSigningKey(ctx context.Context) (string, error) {
@@ -3348,7 +3340,14 @@ func (q *querier) InsertChat(ctx context.Context, arg database.InsertChatParams)
33483340
}
33493341

33503342
func (q *querier) InsertChatMessages(ctx context.Context, arg database.InsertChatMessagesParams) ([]database.ChatMessage, error) {
3351-
return insert(q.log, q.auth, rbac.ResourceChat.WithID(arg.ChatID), q.db.InsertChatMessages)(ctx, arg)
3343+
c, err := q.db.GetChatByID(ctx, arg.ChatID)
3344+
if err != nil {
3345+
return nil, err
3346+
}
3347+
if err := q.authorizeContext(ctx, policy.ActionUpdate, c); err != nil {
3348+
return nil, err
3349+
}
3350+
return q.db.InsertChatMessages(ctx, arg)
33523351
}
33533352

33543353
func (q *querier) InsertCryptoKey(ctx context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
@@ -4000,10 +3999,10 @@ func (q *querier) UpdateAPIKeyByID(ctx context.Context, arg database.UpdateAPIKe
40003999
}
40014000

40024001
func (q *querier) UpdateChatByID(ctx context.Context, arg database.UpdateChatByIDParams) error {
4003-
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceChat.WithID(arg.ID)); err != nil {
4004-
return err
4002+
fetch := func(ctx context.Context, arg database.UpdateChatByIDParams) (database.Chat, error) {
4003+
return q.db.GetChatByID(ctx, arg.ID)
40054004
}
4006-
return q.db.UpdateChatByID(ctx, arg)
4005+
return update(q.log, q.auth, fetch, q.db.UpdateChatByID)(ctx, arg)
40074006
}
40084007

40094008
func (q *querier) UpdateCryptoKeyDeletesAt(ctx context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {

coderd/database/dbauthz/dbauthz_test.go

+73
Original file line numberDiff line numberDiff line change
@@ -5307,3 +5307,76 @@ func (s *MethodTestSuite) TestResourcesProvisionerdserver() {
53075307
}).Asserts(rbac.ResourceWorkspaceAgentDevcontainers, policy.ActionCreate)
53085308
}))
53095309
}
5310+
5311+
func (s *MethodTestSuite) TestChat() {
5312+
createChat := func(t *testing.T, db database.Store) (database.User, database.Chat, database.ChatMessage) {
5313+
t.Helper()
5314+
5315+
usr := dbgen.User(t, db, database.User{})
5316+
chat := dbgen.Chat(s.T(), db, database.Chat{
5317+
OwnerID: usr.ID,
5318+
})
5319+
msg := dbgen.ChatMessage(s.T(), db, database.ChatMessage{
5320+
ChatID: chat.ID,
5321+
})
5322+
5323+
return usr, chat, msg
5324+
}
5325+
5326+
s.Run("DeleteChat", s.Subtest(func(db database.Store, check *expects) {
5327+
_, c, _ := createChat(s.T(), db)
5328+
check.Args(c.ID).Asserts(c, policy.ActionDelete)
5329+
}))
5330+
5331+
s.Run("GetChatByID", s.Subtest(func(db database.Store, check *expects) {
5332+
_, c, _ := createChat(s.T(), db)
5333+
check.Args(c.ID).Asserts(c, policy.ActionRead).Returns(c)
5334+
}))
5335+
5336+
s.Run("GetChatMessagesByChatID", s.Subtest(func(db database.Store, check *expects) {
5337+
_, c, m := createChat(s.T(), db)
5338+
check.Args(c.ID).Asserts(c, policy.ActionRead).Returns([]database.ChatMessage{m})
5339+
}))
5340+
5341+
s.Run("GetChatsByOwnerID", s.Subtest(func(db database.Store, check *expects) {
5342+
u1, u1c1, _ := createChat(s.T(), db)
5343+
u1c2 := dbgen.Chat(s.T(), db, database.Chat{
5344+
OwnerID: u1.ID,
5345+
})
5346+
_, _, _ = createChat(s.T(), db) // other user's chat
5347+
check.Args(u1.ID).Asserts(u1c2, policy.ActionRead, u1c1, policy.ActionRead).Returns([]database.Chat{u1c1, u1c2})
5348+
}))
5349+
5350+
s.Run("InsertChat", s.Subtest(func(db database.Store, check *expects) {
5351+
usr := dbgen.User(s.T(), db, database.User{})
5352+
check.Args(database.InsertChatParams{
5353+
OwnerID: usr.ID,
5354+
Title: "test chat",
5355+
CreatedAt: dbtime.Now(),
5356+
UpdatedAt: dbtime.Now(),
5357+
}).Asserts(rbac.ResourceChat.WithOwner(usr.ID.String()), policy.ActionCreate)
5358+
}))
5359+
5360+
s.Run("InsertChatMessages", s.Subtest(func(db database.Store, check *expects) {
5361+
usr := dbgen.User(s.T(), db, database.User{})
5362+
chat := dbgen.Chat(s.T(), db, database.Chat{
5363+
OwnerID: usr.ID,
5364+
})
5365+
check.Args(database.InsertChatMessagesParams{
5366+
ChatID: chat.ID,
5367+
CreatedAt: dbtime.Now(),
5368+
Model: "test-model",
5369+
Provider: "test-provider",
5370+
Content: []byte(`[]`),
5371+
}).Asserts(chat, policy.ActionUpdate)
5372+
}))
5373+
5374+
s.Run("UpdateChatByID", s.Subtest(func(db database.Store, check *expects) {
5375+
_, c, _ := createChat(s.T(), db)
5376+
check.Args(database.UpdateChatByIDParams{
5377+
ID: c.ID,
5378+
Title: "new title",
5379+
UpdatedAt: dbtime.Now(),
5380+
}).Asserts(c, policy.ActionUpdate)
5381+
}))
5382+
}

coderd/database/dbgen/dbgen.go

+13
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,19 @@ func Chat(t testing.TB, db database.Store, seed database.Chat) database.Chat {
154154
return chat
155155
}
156156

157+
func ChatMessage(t testing.TB, db database.Store, seed database.ChatMessage) database.ChatMessage {
158+
msg, err := db.InsertChatMessages(genCtx, database.InsertChatMessagesParams{
159+
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
160+
ChatID: takeFirst(seed.ChatID, uuid.New()),
161+
Model: takeFirst(seed.Model, "train"),
162+
Provider: takeFirst(seed.Provider, "thomas"),
163+
Content: takeFirstSlice(seed.Content, []byte(`[{"text": "Choo choo!"}]`)),
164+
})
165+
require.NoError(t, err, "insert chat message")
166+
require.Len(t, msg, 1, "insert one chat message did not return exactly one message")
167+
return msg[0]
168+
}
169+
157170
func WorkspaceAgentPortShare(t testing.TB, db database.Store, orig database.WorkspaceAgentPortShare) database.WorkspaceAgentPortShare {
158171
ps, err := db.UpsertWorkspaceAgentPortShare(genCtx, database.UpsertWorkspaceAgentPortShareParams{
159172
WorkspaceID: takeFirst(orig.WorkspaceID, uuid.New()),

coderd/database/dbmem/dbmem.go

+2
Original file line numberDiff line numberDiff line change
@@ -8487,6 +8487,8 @@ func (q *FakeQuerier) InsertChatMessages(ctx context.Context, arg database.Inser
84878487
Content: content,
84888488
})
84898489
}
8490+
8491+
q.chatMessages = append(q.chatMessages, messages...)
84908492
return messages, nil
84918493
}
84928494

coderd/database/modelmethods.go

+5
Original file line numberDiff line numberDiff line change
@@ -568,3 +568,8 @@ func (m WorkspaceAgentVolumeResourceMonitor) Debounce(
568568

569569
return m.DebouncedUntil, false
570570
}
571+
572+
func (c Chat) RBACObject() rbac.Object {
573+
return rbac.ResourceChat.WithID(c.ID).
574+
WithOwner(c.OwnerID.String())
575+
}

coderd/rbac/roles.go

+2
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
299299
ResourceOrganizationMember.Type: {policy.ActionRead},
300300
// Users can create provisioner daemons scoped to themselves.
301301
ResourceProvisionerDaemon.Type: {policy.ActionRead, policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
302+
// Users can create, read, update, and delete their own agentic chat messages.
303+
ResourceChat.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
302304
})...,
303305
),
304306
}.withCachedRegoValue()

coderd/rbac/roles_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,37 @@ func TestRolePermissions(t *testing.T) {
831831
},
832832
},
833833
},
834+
// Members may read their own chats.
835+
{
836+
Name: "CreateReadUpdateDeleteMyChats",
837+
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
838+
Resource: rbac.ResourceChat.WithOwner(currentUser.String()),
839+
AuthorizeMap: map[bool][]hasAuthSubjects{
840+
true: {memberMe, orgMemberMe, owner},
841+
false: {
842+
userAdmin, orgUserAdmin, templateAdmin,
843+
orgAuditor, orgTemplateAdmin,
844+
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
845+
orgAdmin, otherOrgAdmin,
846+
},
847+
},
848+
},
849+
// Only owners can create, read, update, and delete other users' chats.
850+
{
851+
Name: "CreateReadUpdateDeleteOtherUserChats",
852+
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
853+
Resource: rbac.ResourceChat.WithOwner(uuid.NewString()), // some other user
854+
AuthorizeMap: map[bool][]hasAuthSubjects{
855+
true: {owner},
856+
false: {
857+
memberMe, orgMemberMe,
858+
userAdmin, orgUserAdmin, templateAdmin,
859+
orgAuditor, orgTemplateAdmin,
860+
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
861+
orgAdmin, otherOrgAdmin,
862+
},
863+
},
864+
},
834865
}
835866

836867
// We expect every permission to be tested above.

0 commit comments

Comments
 (0)