fix(agent): restrict permission field edits to canvas owner only (#17185)

This commit is contained in:
euvre
2026-07-22 12:09:22 +08:00
committed by GitHub
parent 03e583f9bc
commit fe3e87361d
2 changed files with 94 additions and 0 deletions

View File

@@ -581,12 +581,23 @@ func (s *AgentService) GetAgent(ctx context.Context, userID, canvasID string) (*
// UpdateAgent applies a draft patch to user_canvas. Settings updates may omit
// dsl; in that case the existing draft DSL must be preserved.
//
// Permission is an owner-only setting: team members who have access to the
// canvas can still update title/avatar/description, but any permission value
// they send is ignored so they cannot make a team agent private (or vice
// versa). The owner can change permission together with title/avatar in one
// request.
func (s *AgentService) UpdateAgent(ctx context.Context, userID, canvasID string, patch map[string]interface{}) error {
canvasInstance, err := s.loadCanvasForUser(ctx, userID, canvasID)
if err != nil {
return err
}
// Only the canvas owner may change the permission field.
if _, ok := patch["permission"]; ok && canvasInstance.UserID != userID {
delete(patch, "permission")
}
updates := map[string]interface{}{}
for _, key := range []string{"title", "avatar", "description", "permission", "canvas_type", "canvas_category"} {
if value, ok := patch[key]; ok && value != nil {

View File

@@ -1469,6 +1469,89 @@ func TestUpdateAgentSettingsPreservesDSL(t *testing.T) {
}
}
func TestUpdateAgentPermissionOwnerOnly(t *testing.T) {
setupAgentSessionServiceTest(t)
status := "1"
if err := dao.DB.Create(&entity.UserTenant{
ID: "ut-owner",
UserID: "user-1",
TenantID: "user-1",
Role: "owner",
InvitedBy: "user-1",
Status: &status,
}).Error; err != nil {
t.Fatalf("failed to seed owner tenant: %v", err)
}
if err := dao.DB.Create(&entity.UserTenant{
ID: "ut-member",
UserID: "user-2",
TenantID: "user-1",
Role: "normal",
InvitedBy: "user-1",
Status: &status,
}).Error; err != nil {
t.Fatalf("failed to seed member tenant: %v", err)
}
if err := dao.DB.Create(&entity.UserCanvas{
ID: "canvas-team",
UserID: "user-1",
Title: sptr("Team Agent"),
Avatar: sptr("owner-avatar"),
Permission: "team",
CanvasCategory: "agent_canvas",
DSL: entity.JSONMap{},
}).Error; err != nil {
t.Fatalf("failed to seed canvas: %v", err)
}
// Team member tries to make the agent private while updating title/avatar.
err := NewAgentService().UpdateAgent(context.Background(), "user-2", "canvas-team", map[string]interface{}{
"title": "Renamed by member",
"avatar": "member-avatar",
"permission": "me",
})
if err != nil {
t.Fatalf("team member update should succeed for non-permission fields: %v", err)
}
persisted, err := dao.NewUserCanvasDAO().GetByID("canvas-team")
if err != nil {
t.Fatalf("failed to reload canvas: %v", err)
}
if persisted.Permission != "team" {
t.Fatalf("team member changed permission to %q; want team", persisted.Permission)
}
if persisted.Title == nil || *persisted.Title != "Renamed by member" {
t.Fatalf("title = %v, want Renamed by member", persisted.Title)
}
if persisted.Avatar == nil || *persisted.Avatar != "member-avatar" {
t.Fatalf("avatar = %v, want member-avatar", persisted.Avatar)
}
// Owner can change permission together with title/avatar.
err = NewAgentService().UpdateAgent(context.Background(), "user-1", "canvas-team", map[string]interface{}{
"title": "Owner updated",
"avatar": "owner-avatar-2",
"permission": "me",
})
if err != nil {
t.Fatalf("owner update failed: %v", err)
}
persisted, err = dao.NewUserCanvasDAO().GetByID("canvas-team")
if err != nil {
t.Fatalf("failed to reload canvas: %v", err)
}
if persisted.Permission != "me" {
t.Fatalf("owner permission = %q, want me", persisted.Permission)
}
if persisted.Title == nil || *persisted.Title != "Owner updated" {
t.Fatalf("title = %v, want Owner updated", persisted.Title)
}
if persisted.Avatar == nil || *persisted.Avatar != "owner-avatar-2" {
t.Fatalf("avatar = %v, want owner-avatar-2", persisted.Avatar)
}
}
func TestUpdateAgentPersistsDSLAsJSONMap(t *testing.T) {
setupAgentSessionServiceTest(t)