Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions coderd/database/check_constraint.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,9 @@ var (
// user's personal data; token writes use the per-user
// AsChatdTokenOwner subject instead.
rbac.ResourceUser.Type: {policy.ActionReadPersonal},
// TODO(mafredri): remove this organization read after CODAGT-709 M3.
// exp_chats and chatd use it for the pre-cutover default-org fallback.
rbac.ResourceOrganization.Type: {policy.ActionRead},
}),
User: []rbac.Permission{},
ByOrgID: map[string]rbac.OrgPermissions{},
Expand Down Expand Up @@ -3751,7 +3754,7 @@ func (q *querier) GetDatabaseNow(ctx context.Context) (time.Time, error) {
return q.db.GetDatabaseNow(ctx)
}

func (q *querier) GetDefaultChatModelConfig(ctx context.Context) (database.ChatModelConfig, error) {
func (q *querier) GetDefaultChatModelConfig(ctx context.Context, organizationID uuid.UUID) (database.ChatModelConfig, error) {
// Reading the default model config is needed for chat creation.
// TODO(CODAGT-161): scope this check when org context is available.
// This function has no org context to scope the check, and
Expand All @@ -3761,7 +3764,7 @@ func (q *querier) GetDefaultChatModelConfig(ctx context.Context) (database.ChatM
if _, ok := ActorFromContext(ctx); !ok {
return database.ChatModelConfig{}, ErrNoActor
}
return q.db.GetDefaultChatModelConfig(ctx)
return q.db.GetDefaultChatModelConfig(ctx, organizationID)
}

func (q *querier) GetDefaultOrganization(ctx context.Context) (database.Organization, error) {
Expand Down Expand Up @@ -3810,6 +3813,14 @@ func (q *querier) GetEnabledChatModelConfigs(ctx context.Context) ([]database.Ge
return q.db.GetEnabledChatModelConfigs(ctx)
}

func (q *querier) GetEnabledChatModelConfigsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]database.GetEnabledChatModelConfigsByOrganizationRow, error) {
Comment thread
ethanndickson marked this conversation as resolved.
// TODO(CODAGT-161): authorize against an organization-scoped chat model config resource.
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
return nil, err
}
return q.db.GetEnabledChatModelConfigsByOrganization(ctx, organizationID)
}

func (q *querier) GetEnabledMCPServerConfigsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]database.MCPServerConfig, error) {
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetEnabledMCPServerConfigsByOrganization)(ctx, organizationID)
}
Expand Down Expand Up @@ -7302,11 +7313,11 @@ func (q *querier) UnpinChatByID(ctx context.Context, id uuid.UUID) error {
return q.db.UnpinChatByID(ctx, id)
}

func (q *querier) UnsetDefaultChatModelConfigs(ctx context.Context) error {
func (q *querier) UnsetDefaultChatModelConfigs(ctx context.Context, organizationID uuid.UUID) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
return err
}
return q.db.UnsetDefaultChatModelConfigs(ctx)
return q.db.UnsetDefaultChatModelConfigs(ctx, organizationID)
}

func (q *querier) UpdateAIBridgeInterceptionEnded(ctx context.Context, params database.UpdateAIBridgeInterceptionEndedParams) (database.AIBridgeInterception, error) {
Expand Down
32 changes: 19 additions & 13 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1080,8 +1080,8 @@ func (s *MethodTestSuite) TestChats() {
}))
s.Run("GetDefaultChatModelConfig", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
config := testutil.Fake(s.T(), faker, database.ChatModelConfig{})
dbm.EXPECT().GetDefaultChatModelConfig(gomock.Any()).Return(config, nil).AnyTimes()
check.Asserts().Returns(config)
dbm.EXPECT().GetDefaultChatModelConfig(gomock.Any(), config.OrganizationID).Return(config, nil).AnyTimes()
check.Args(config.OrganizationID).Asserts().Returns(config)
}))
s.Run("GetChatModelConfigs", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
configA := testutil.Fake(s.T(), faker, database.ChatModelConfig{})
Expand Down Expand Up @@ -1203,6 +1203,14 @@ func (s *MethodTestSuite) TestChats() {
check.Args().Asserts(rbac.ResourceDeploymentConfig, policy.ActionRead).Returns([]database.GetEnabledChatModelConfigsRow{rowA, rowB})
}))

s.Run("GetEnabledChatModelConfigsByOrganization", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
orgID := uuid.New()
rowA := testutil.Fake(s.T(), faker, database.GetEnabledChatModelConfigsByOrganizationRow{})
rowB := testutil.Fake(s.T(), faker, database.GetEnabledChatModelConfigsByOrganizationRow{})
dbm.EXPECT().GetEnabledChatModelConfigsByOrganization(gomock.Any(), orgID).Return([]database.GetEnabledChatModelConfigsByOrganizationRow{rowA, rowB}, nil).AnyTimes()
check.Args(orgID).Asserts(rbac.ResourceDeploymentConfig, policy.ActionRead).Returns([]database.GetEnabledChatModelConfigsByOrganizationRow{rowA, rowB})
}))

s.Run("GetStaleChats", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
threshold := dbtime.Now()
chats := []database.Chat{testutil.Fake(s.T(), faker, database.Chat{})}
Expand Down Expand Up @@ -1527,8 +1535,9 @@ func (s *MethodTestSuite) TestChats() {
check.Args(arg).Asserts(chat, policy.ActionUpdate).Returns(updatedChat)
}))
s.Run("UnsetDefaultChatModelConfigs", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().UnsetDefaultChatModelConfigs(gomock.Any()).Return(nil).AnyTimes()
check.Args().Asserts(rbac.ResourceSystem, policy.ActionUpdate)
orgID := uuid.New()
dbm.EXPECT().UnsetDefaultChatModelConfigs(gomock.Any(), orgID).Return(nil).AnyTimes()
check.Args(orgID).Asserts(rbac.ResourceSystem, policy.ActionUpdate)
}))
s.Run("UpsertChatDiffStatus", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
chat := testutil.Fake(s.T(), faker, database.Chat{})
Expand Down Expand Up @@ -7620,10 +7629,8 @@ func TestAsChatd(t *testing.T) {
err = auth.Authorize(ctx, actor, policy.ActionUpdate, rbac.ResourceDeploymentConfig)
require.Error(t, err, "deployment config update should not be allowed")

// Pin the complete ResourceUser action set: read_personal (user
// chat custom prompts) only. Token refresh persistence uses the
// per-user AsChatdTokenOwner subject, so a future site-wide
// personal-write grant fails here.
// Pin the complete ResourceUser action set: read_personal only.
// Token refresh persistence uses the per-user AsChatdTokenOwner subject.
for _, action := range rbac.ResourceUser.AvailableActions() {
err := auth.Authorize(ctx, actor, action, rbac.ResourceUser)
if action == policy.ActionReadPersonal {
Expand All @@ -7632,6 +7639,10 @@ func TestAsChatd(t *testing.T) {
require.Error(t, err, "user %s should be denied", action)
}
}

// Organization read is temporarily needed for the pre-cutover default-org fallback.
err = auth.Authorize(ctx, actor, policy.ActionRead, rbac.ResourceOrganization)
require.NoError(t, err, "organization read should be allowed")
})

t.Run("DeniedActions", func(t *testing.T) {
Expand All @@ -7652,11 +7663,6 @@ func TestAsChatd(t *testing.T) {
// Cannot access provisioner daemons.
err = auth.Authorize(ctx, actor, policy.ActionRead, rbac.ResourceProvisionerDaemon)
require.Error(t, err, "provisioner daemon read should be denied")

// Cannot access organizations; MCP server config resolution is
// strictly org-scoped and needs no organization reads.
err = auth.Authorize(ctx, actor, policy.ActionRead, rbac.ResourceOrganization)
require.Error(t, err, "organization read should be denied")
})
}

Expand Down
16 changes: 16 additions & 0 deletions coderd/database/dbgen/dbgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,19 @@ func ChatModelConfig(t testing.TB, db database.Store, seed database.ChatModelCon
}
aiProviderID = uuid.NullUUID{UUID: provider.ID, Valid: true}
}
organizationID := seed.OrganizationID
if organizationID == uuid.Nil {
defaultOrg, err := db.GetDefaultOrganization(genCtx)
require.NoError(t, err, "get default organization")
organizationID = defaultOrg.ID
}
groupACL := seed.GroupACL
Comment thread
ethanndickson marked this conversation as resolved.
if groupACL == nil {
groupACL = database.ChatACL{
organizationID.String(): {Permissions: []policy.Action{policy.ActionRead}},
}
}
userACL := seed.UserACL
params := database.InsertChatModelConfigParams{
Model: takeFirst(seed.Model, "gpt-4o-mini"),
DisplayName: takeFirst(seed.DisplayName, "Test Model"),
Expand All @@ -189,6 +202,9 @@ func ChatModelConfig(t testing.TB, db database.Store, seed database.ChatModelCon
CompressionThreshold: takeFirst(seed.CompressionThreshold, defaultChatModelCompressionThreshold),
Options: takeFirstSlice(seed.Options, json.RawMessage(`{}`)),
AIProviderID: aiProviderID,
OrganizationID: organizationID,
GroupACL: groupACL,
UserACL: userACL,
}
for _, fn := range munge {
fn(&params)
Expand Down
16 changes: 12 additions & 4 deletions coderd/database/dbmetrics/querymetrics.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 23 additions & 8 deletions coderd/database/dbmock/dbmock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 12 additions & 2 deletions coderd/database/dump.sql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading