mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
refactor(dbauthz): add authz for system-level functions (#6513)
- Introduces rbac.ResourceSystem - Grants system.* to system and provisionerd rbac subjects - Updates dbauthz system queries where applicable - coderd: Avoid index out of bounds in api.workspaceBuilds - dbauthz: move GetUsersByIDs out of system, modify RBAC check to ResourceUser - workspaceapps: Add test case for when owner of app is not found
This commit is contained in:
+2
-1
@@ -795,7 +795,8 @@ func (api *API) CreateInMemoryProvisionerDaemon(ctx context.Context, debounce ti
|
||||
}()
|
||||
|
||||
name := namesgenerator.GetRandomName(1)
|
||||
daemon, err := api.Database.InsertProvisionerDaemon(ctx, database.InsertProvisionerDaemonParams{
|
||||
// nolint:gocritic // Inserting a provisioner daemon is a system function.
|
||||
daemon, err := api.Database.InsertProvisionerDaemon(dbauthz.AsSystemRestricted(ctx), database.InsertProvisionerDaemonParams{
|
||||
ID: uuid.New(),
|
||||
CreatedAt: database.Now(),
|
||||
Name: name,
|
||||
|
||||
@@ -115,17 +115,17 @@ func ActorFromContext(ctx context.Context) (rbac.Subject, bool) {
|
||||
return a, ok
|
||||
}
|
||||
|
||||
// AsProvisionerd returns a context with an actor that has permissions required
|
||||
// for provisionerd to function.
|
||||
func AsProvisionerd(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, authContextKey{}, rbac.Subject{
|
||||
var (
|
||||
subjectProvisionerd = rbac.Subject{
|
||||
ID: uuid.Nil.String(),
|
||||
Roles: rbac.Roles([]rbac.Role{
|
||||
{
|
||||
Name: "provisionerd",
|
||||
DisplayName: "Provisioner Daemon",
|
||||
Site: rbac.Permissions(map[string][]rbac.Action{
|
||||
// TODO: Add ProvisionerJob resource type.
|
||||
rbac.ResourceFile.Type: {rbac.ActionRead},
|
||||
rbac.ResourceSystem.Type: {rbac.WildcardSymbol},
|
||||
rbac.ResourceTemplate.Type: {rbac.ActionRead, rbac.ActionUpdate},
|
||||
rbac.ResourceUser.Type: {rbac.ActionRead},
|
||||
rbac.ResourceWorkspace.Type: {rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
@@ -135,20 +135,15 @@ func AsProvisionerd(ctx context.Context) context.Context {
|
||||
},
|
||||
}),
|
||||
Scope: rbac.ScopeAll,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// AsAutostart returns a context with an actor that has permissions required
|
||||
// for autostart to function.
|
||||
func AsAutostart(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, authContextKey{}, rbac.Subject{
|
||||
}
|
||||
subjectAutostart = rbac.Subject{
|
||||
ID: uuid.Nil.String(),
|
||||
Roles: rbac.Roles([]rbac.Role{
|
||||
{
|
||||
Name: "autostart",
|
||||
DisplayName: "Autostart Daemon",
|
||||
Site: rbac.Permissions(map[string][]rbac.Action{
|
||||
rbac.ResourceSystem.Type: {rbac.WildcardSymbol},
|
||||
rbac.ResourceTemplate.Type: {rbac.ActionRead, rbac.ActionUpdate},
|
||||
rbac.ResourceWorkspace.Type: {rbac.ActionRead, rbac.ActionUpdate},
|
||||
}),
|
||||
@@ -157,14 +152,8 @@ func AsAutostart(ctx context.Context) context.Context {
|
||||
},
|
||||
}),
|
||||
Scope: rbac.ScopeAll,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// AsSystemRestricted returns a context with an actor that has permissions
|
||||
// required for various system operations (login, logout, metrics cache).
|
||||
func AsSystemRestricted(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, authContextKey{}, rbac.Subject{
|
||||
}
|
||||
subjectSystemRestricted = rbac.Subject{
|
||||
ID: uuid.Nil.String(),
|
||||
Roles: rbac.Roles([]rbac.Role{
|
||||
{
|
||||
@@ -175,6 +164,7 @@ func AsSystemRestricted(ctx context.Context) context.Context {
|
||||
rbac.ResourceAPIKey.Type: {rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
|
||||
rbac.ResourceGroup.Type: {rbac.ActionCreate, rbac.ActionUpdate},
|
||||
rbac.ResourceRoleAssignment.Type: {rbac.ActionCreate},
|
||||
rbac.ResourceSystem.Type: {rbac.WildcardSymbol},
|
||||
rbac.ResourceOrganization.Type: {rbac.ActionCreate},
|
||||
rbac.ResourceOrganizationMember.Type: {rbac.ActionCreate},
|
||||
rbac.ResourceOrgRoleAssignment.Type: {rbac.ActionCreate},
|
||||
@@ -187,8 +177,25 @@ func AsSystemRestricted(ctx context.Context) context.Context {
|
||||
},
|
||||
}),
|
||||
Scope: rbac.ScopeAll,
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// AsProvisionerd returns a context with an actor that has permissions required
|
||||
// for provisionerd to function.
|
||||
func AsProvisionerd(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, authContextKey{}, subjectProvisionerd)
|
||||
}
|
||||
|
||||
// AsAutostart returns a context with an actor that has permissions required
|
||||
// for autostart to function.
|
||||
func AsAutostart(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, authContextKey{}, subjectAutostart)
|
||||
}
|
||||
|
||||
// AsSystemRestricted returns a context with an actor that has permissions
|
||||
// required for various system operations (login, logout, metrics cache).
|
||||
func AsSystemRestricted(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, authContextKey{}, subjectSystemRestricted)
|
||||
}
|
||||
|
||||
var AsRemoveActor = rbac.Subject{
|
||||
|
||||
@@ -963,6 +963,18 @@ func (q *querier) GetUserByID(ctx context.Context, id uuid.UUID) (database.User,
|
||||
return fetch(q.log, q.auth, q.db.GetUserByID)(ctx, id)
|
||||
}
|
||||
|
||||
// GetUsersByIDs is only used for usernames on workspace return data.
|
||||
// This function should be replaced by joining this data to the workspace query
|
||||
// itself.
|
||||
func (q *querier) GetUsersByIDs(ctx context.Context, ids []uuid.UUID) ([]database.User, error) {
|
||||
for _, uid := range ids {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceUser.WithID(uid)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q.db.GetUsersByIDs(ctx, ids)
|
||||
}
|
||||
|
||||
func (q *querier) GetAuthorizedUserCount(ctx context.Context, arg database.GetFilteredUserCountParams, prepared rbac.PreparedAuthorized) (int64, error) {
|
||||
return q.db.GetAuthorizedUserCount(ctx, arg, prepared)
|
||||
}
|
||||
|
||||
@@ -282,11 +282,6 @@ func (s *MethodTestSuite) TestProvsionerJob() {
|
||||
check.Args(database.UpdateProvisionerJobWithCancelByIDParams{ID: j.ID}).
|
||||
Asserts(v.RBACObject(tpl), []rbac.Action{rbac.ActionRead, rbac.ActionUpdate}).Returns()
|
||||
}))
|
||||
s.Run("GetProvisionerJobsByIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
a := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{})
|
||||
b := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{})
|
||||
check.Args([]uuid.UUID{a.ID, b.ID}).Asserts().Returns(slice.New(a, b))
|
||||
}))
|
||||
s.Run("GetProvisionerLogsByIDBetween", s.Subtest(func(db database.Store, check *expects) {
|
||||
w := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
j := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{
|
||||
@@ -619,22 +614,6 @@ func (s *MethodTestSuite) TestTemplate() {
|
||||
})
|
||||
check.Args(tv.ID).Asserts(t1, rbac.ActionRead).Returns(tv)
|
||||
}))
|
||||
s.Run("GetTemplateVersionsByIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
t1 := dbgen.Template(s.T(), db, database.Template{})
|
||||
t2 := dbgen.Template(s.T(), db, database.Template{})
|
||||
tv1 := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: t1.ID, Valid: true},
|
||||
})
|
||||
tv2 := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: t2.ID, Valid: true},
|
||||
})
|
||||
tv3 := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: t2.ID, Valid: true},
|
||||
})
|
||||
check.Args([]uuid.UUID{tv1.ID, tv2.ID, tv3.ID}).
|
||||
Asserts( /*t1, rbac.ActionRead, t2, rbac.ActionRead*/ ).
|
||||
Returns(slice.New(tv1, tv2, tv3))
|
||||
}))
|
||||
s.Run("GetTemplateVersionsByTemplateID", s.Subtest(func(db database.Store, check *expects) {
|
||||
t1 := dbgen.Template(s.T(), db, database.Template{})
|
||||
a := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
|
||||
@@ -784,6 +763,13 @@ func (s *MethodTestSuite) TestUser() {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
check.Args(u.ID).Asserts(u, rbac.ActionRead).Returns(u)
|
||||
}))
|
||||
s.Run("GetUsersByIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
a := dbgen.User(s.T(), db, database.User{CreatedAt: database.Now().Add(-time.Hour)})
|
||||
b := dbgen.User(s.T(), db, database.User{CreatedAt: database.Now()})
|
||||
check.Args([]uuid.UUID{a.ID, b.ID}).
|
||||
Asserts(a, rbac.ActionRead, b, rbac.ActionRead).
|
||||
Returns(slice.New(a, b))
|
||||
}))
|
||||
s.Run("GetAuthorizedUserCount", s.Subtest(func(db database.Store, check *expects) {
|
||||
_ = dbgen.User(s.T(), db, database.User{})
|
||||
check.Args(database.GetFilteredUserCountParams{}, emptyPreparedAuthorized{}).Asserts().Returns(int64(1))
|
||||
@@ -803,13 +789,6 @@ func (s *MethodTestSuite) TestUser() {
|
||||
b := dbgen.User(s.T(), db, database.User{CreatedAt: database.Now()})
|
||||
check.Args(database.GetUsersParams{}).Asserts(a, rbac.ActionRead, b, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetUsersByIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
a := dbgen.User(s.T(), db, database.User{CreatedAt: database.Now().Add(-time.Hour)})
|
||||
b := dbgen.User(s.T(), db, database.User{CreatedAt: database.Now()})
|
||||
check.Args([]uuid.UUID{a.ID, b.ID}).
|
||||
Asserts( /*a, rbac.ActionRead, b, rbac.ActionRead*/ ).
|
||||
Returns(slice.New(a, b))
|
||||
}))
|
||||
s.Run("InsertUser", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.InsertUserParams{
|
||||
ID: uuid.New(),
|
||||
@@ -977,14 +956,6 @@ func (s *MethodTestSuite) TestWorkspace() {
|
||||
agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID})
|
||||
check.Args(agt.AuthInstanceID.String).Asserts(ws, rbac.ActionRead).Returns(agt)
|
||||
}))
|
||||
s.Run("GetWorkspaceAgentsByResourceIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
||||
res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
|
||||
agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID})
|
||||
check.Args([]uuid.UUID{res.ID}).Asserts( /*ws, rbac.ActionRead*/ ).
|
||||
Returns([]database.WorkspaceAgent{agt})
|
||||
}))
|
||||
s.Run("UpdateWorkspaceAgentLifecycleStateByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
||||
@@ -1026,23 +997,6 @@ func (s *MethodTestSuite) TestWorkspace() {
|
||||
|
||||
check.Args(agt.ID).Asserts(ws, rbac.ActionRead).Returns(slice.New(a, b))
|
||||
}))
|
||||
s.Run("GetWorkspaceAppsByAgentIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
aWs := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
aBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: aWs.ID, JobID: uuid.New()})
|
||||
aRes := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: aBuild.JobID})
|
||||
aAgt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: aRes.ID})
|
||||
a := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: aAgt.ID})
|
||||
|
||||
bWs := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
bBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: bWs.ID, JobID: uuid.New()})
|
||||
bRes := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: bBuild.JobID})
|
||||
bAgt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: bRes.ID})
|
||||
b := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: bAgt.ID})
|
||||
|
||||
check.Args([]uuid.UUID{a.AgentID, b.AgentID}).
|
||||
Asserts( /*aWs, rbac.ActionRead, bWs, rbac.ActionRead*/ ).
|
||||
Returns([]database.WorkspaceApp{a, b})
|
||||
}))
|
||||
s.Run("GetWorkspaceBuildByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID})
|
||||
@@ -1096,15 +1050,6 @@ func (s *MethodTestSuite) TestWorkspace() {
|
||||
res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
|
||||
check.Args(res.ID).Asserts(ws, rbac.ActionRead).Returns(res)
|
||||
}))
|
||||
s.Run("GetWorkspaceResourceMetadataByResourceIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
||||
_ = dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild})
|
||||
a := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
|
||||
b := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
|
||||
check.Args([]uuid.UUID{a.ID, b.ID}).
|
||||
Asserts( /*ws, []rbac.Action{rbac.ActionRead, rbac.ActionRead}*/ )
|
||||
}))
|
||||
s.Run("Build/GetWorkspaceResourcesByJobID", s.Subtest(func(db database.Store, check *expects) {
|
||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
||||
@@ -1117,18 +1062,6 @@ func (s *MethodTestSuite) TestWorkspace() {
|
||||
job := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: v.JobID, Type: database.ProvisionerJobTypeTemplateVersionImport})
|
||||
check.Args(job.ID).Asserts(v.RBACObject(tpl), []rbac.Action{rbac.ActionRead, rbac.ActionRead}).Returns([]database.WorkspaceResource{})
|
||||
}))
|
||||
s.Run("GetWorkspaceResourcesByJobIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
tpl := dbgen.Template(s.T(), db, database.Template{})
|
||||
v := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true}, JobID: uuid.New()})
|
||||
tJob := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: v.JobID, Type: database.ProvisionerJobTypeTemplateVersionImport})
|
||||
|
||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
||||
wJob := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild})
|
||||
check.Args([]uuid.UUID{tJob.ID, wJob.ID}).
|
||||
Asserts( /*v.RBACObject(tpl), rbac.ActionRead, ws, rbac.ActionRead*/ ).
|
||||
Returns([]database.WorkspaceResource{})
|
||||
}))
|
||||
s.Run("InsertWorkspace", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
o := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
|
||||
@@ -7,53 +7,50 @@ import (
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
)
|
||||
|
||||
// TODO: All these system functions should have rbac objects created to allow
|
||||
// only system roles to call them. No user roles should ever have the permission
|
||||
// to these objects. Might need a negative permission on the `Owner` role to
|
||||
// prevent owners.
|
||||
|
||||
// GetWorkspaceAppsByAgentIDs
|
||||
// The workspace/job is already fetched.
|
||||
// TODO: This function should be removed/replaced with something with proper auth.
|
||||
func (q *querier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceApp, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWorkspaceAppsByAgentIDs(ctx, ids)
|
||||
}
|
||||
|
||||
// GetWorkspaceAgentsByResourceIDs
|
||||
// The workspace/job is already fetched.
|
||||
// TODO: This function should be removed/replaced with something with proper auth.
|
||||
func (q *querier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceAgent, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWorkspaceAgentsByResourceIDs(ctx, ids)
|
||||
}
|
||||
|
||||
// GetWorkspaceResourceMetadataByResourceIDs is only used for build data.
|
||||
// The workspace/job is already fetched.
|
||||
// TODO: This function should be removed/replaced with something with proper auth.
|
||||
func (q *querier) GetWorkspaceResourceMetadataByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceResourceMetadatum, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWorkspaceResourceMetadataByResourceIDs(ctx, ids)
|
||||
}
|
||||
|
||||
// GetUsersByIDs is only used for usernames on workspace return data.
|
||||
// This function should be replaced by joining this data to the workspace query
|
||||
// itself.
|
||||
// TODO: This function should be removed/replaced with something with proper auth.
|
||||
// A SQL compiled filter is an option.
|
||||
func (q *querier) GetUsersByIDs(ctx context.Context, ids []uuid.UUID) ([]database.User, error) {
|
||||
return q.db.GetUsersByIDs(ctx, ids)
|
||||
}
|
||||
|
||||
// TODO: we need to add a provisioner job resource
|
||||
func (q *querier) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) {
|
||||
// TODO: This is missing authorization and is incorrect. This call is used by telemetry, and by 1 http route.
|
||||
// That http handler should find a better way to fetch these jobs with easier rbac authz.
|
||||
// if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
return q.db.GetProvisionerJobsByIDs(ctx, ids)
|
||||
}
|
||||
|
||||
// GetTemplateVersionsByIDs is only used for workspace build data.
|
||||
// The workspace is already fetched.
|
||||
// TODO: Find a way to replace this with proper authz.
|
||||
func (q *querier) GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UUID) ([]database.TemplateVersion, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetTemplateVersionsByIDs(ctx, ids)
|
||||
}
|
||||
|
||||
@@ -61,18 +58,30 @@ func (q *querier) GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UUID)
|
||||
// The workspace is already fetched.
|
||||
// TODO: Find a way to replace this with proper authz.
|
||||
func (q *querier) GetWorkspaceResourcesByJobIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceResource, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWorkspaceResourcesByJobIDs(ctx, ids)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateUserLinkedID(ctx context.Context, arg database.UpdateUserLinkedIDParams) (database.UserLink, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
return database.UserLink{}, err
|
||||
}
|
||||
return q.db.UpdateUserLinkedID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetUserLinkByLinkedID(ctx context.Context, linkedID string) (database.UserLink, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return database.UserLink{}, err
|
||||
}
|
||||
return q.db.GetUserLinkByLinkedID(ctx, linkedID)
|
||||
}
|
||||
|
||||
func (q *querier) GetUserLinkByUserIDLoginType(ctx context.Context, arg database.GetUserLinkByUserIDLoginTypeParams) (database.UserLink, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return database.UserLink{}, err
|
||||
}
|
||||
return q.db.GetUserLinkByUserIDLoginType(ctx, arg)
|
||||
}
|
||||
|
||||
@@ -81,96 +90,148 @@ func (q *querier) GetLatestWorkspaceBuilds(ctx context.Context) ([]database.Work
|
||||
// This is because we need to query for all related workspaces to the returned builds.
|
||||
// This is a very inefficient method of fetching the latest workspace builds.
|
||||
// We should just join the rbac properties.
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetLatestWorkspaceBuilds(ctx)
|
||||
}
|
||||
|
||||
// GetWorkspaceAgentByAuthToken is used in http middleware to get the workspace agent.
|
||||
// This should only be used by a system user in that middleware.
|
||||
func (q *querier) GetWorkspaceAgentByAuthToken(ctx context.Context, authToken uuid.UUID) (database.WorkspaceAgent, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return database.WorkspaceAgent{}, err
|
||||
}
|
||||
return q.db.GetWorkspaceAgentByAuthToken(ctx, authToken)
|
||||
}
|
||||
|
||||
func (q *querier) GetActiveUserCount(ctx context.Context) (int64, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return q.db.GetActiveUserCount(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetUnexpiredLicenses(ctx context.Context) ([]database.License, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetUnexpiredLicenses(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (database.GetAuthorizationUserRolesRow, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return database.GetAuthorizationUserRolesRow{}, err
|
||||
}
|
||||
return q.db.GetAuthorizationUserRoles(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) GetDERPMeshKey(ctx context.Context) (string, error) {
|
||||
// TODO Implement authz check for system user.
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return q.db.GetDERPMeshKey(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) InsertDERPMeshKey(ctx context.Context, value string) error {
|
||||
// TODO Implement authz check for system user.
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.InsertDERPMeshKey(ctx, value)
|
||||
}
|
||||
|
||||
func (q *querier) InsertDeploymentID(ctx context.Context, value string) error {
|
||||
// TODO Implement authz check for system user.
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.InsertDeploymentID(ctx, value)
|
||||
}
|
||||
|
||||
func (q *querier) InsertReplica(ctx context.Context, arg database.InsertReplicaParams) (database.Replica, error) {
|
||||
// TODO Implement authz check for system user.
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return database.Replica{}, err
|
||||
}
|
||||
return q.db.InsertReplica(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateReplica(ctx context.Context, arg database.UpdateReplicaParams) (database.Replica, error) {
|
||||
// TODO Implement authz check for system user.
|
||||
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
return database.Replica{}, err
|
||||
}
|
||||
return q.db.UpdateReplica(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error {
|
||||
// TODO Implement authz check for system user.
|
||||
if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.DeleteReplicasUpdatedBefore(ctx, updatedAt)
|
||||
}
|
||||
|
||||
func (q *querier) GetReplicasUpdatedAfter(ctx context.Context, updatedAt time.Time) ([]database.Replica, error) {
|
||||
// TODO Implement authz check for system user.
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetReplicasUpdatedAfter(ctx, updatedAt)
|
||||
}
|
||||
|
||||
func (q *querier) GetUserCount(ctx context.Context) (int64, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return q.db.GetUserCount(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetTemplates(ctx context.Context) ([]database.Template, error) {
|
||||
// TODO Implement authz check for system user.
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetTemplates(ctx)
|
||||
}
|
||||
|
||||
// Only used by metrics cache.
|
||||
func (q *querier) GetTemplateAverageBuildTime(ctx context.Context, arg database.GetTemplateAverageBuildTimeParams) (database.GetTemplateAverageBuildTimeRow, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return database.GetTemplateAverageBuildTimeRow{}, err
|
||||
}
|
||||
return q.db.GetTemplateAverageBuildTime(ctx, arg)
|
||||
}
|
||||
|
||||
// Only used by metrics cache.
|
||||
func (q *querier) GetTemplateDAUs(ctx context.Context, templateID uuid.UUID) ([]database.GetTemplateDAUsRow, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetTemplateDAUs(ctx, templateID)
|
||||
}
|
||||
|
||||
// Only used by metrics cache.
|
||||
func (q *querier) GetDeploymentDAUs(ctx context.Context) ([]database.GetDeploymentDAUsRow, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetDeploymentDAUs(ctx)
|
||||
}
|
||||
|
||||
// UpdateWorkspaceBuildCostByID is used by the provisioning system to update the cost of a workspace build.
|
||||
func (q *querier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) (database.WorkspaceBuild, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
return database.WorkspaceBuild{}, err
|
||||
}
|
||||
return q.db.UpdateWorkspaceBuildCostByID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertOrUpdateLastUpdateCheck(ctx context.Context, value string) error {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.InsertOrUpdateLastUpdateCheck(ctx, value)
|
||||
}
|
||||
|
||||
func (q *querier) GetLastUpdateCheck(ctx context.Context) (string, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return q.db.GetLastUpdateCheck(ctx)
|
||||
}
|
||||
|
||||
@@ -178,26 +239,44 @@ func (q *querier) GetLastUpdateCheck(ctx context.Context) (string, error) {
|
||||
// telemetry data. Never called by a user.
|
||||
|
||||
func (q *querier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceBuild, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWorkspaceBuildsCreatedAfter(ctx, createdAt)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceAgent, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWorkspaceAgentsCreatedAfter(ctx, createdAt)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceApp, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWorkspaceAppsCreatedAfter(ctx, createdAt)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceResourcesCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceResource, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWorkspaceResourcesCreatedAfter(ctx, createdAt)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceResourceMetadataCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceResourceMetadatum, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWorkspaceResourceMetadataCreatedAfter(ctx, createdAt)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteOldWorkspaceAgentStats(ctx context.Context) error {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.DeleteOldWorkspaceAgentStats(ctx)
|
||||
}
|
||||
|
||||
@@ -210,63 +289,115 @@ func (q *querier) GetDeploymentWorkspaceStats(ctx context.Context) (database.Get
|
||||
}
|
||||
|
||||
func (q *querier) GetParameterSchemasCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.ParameterSchema, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetParameterSchemasCreatedAfter(ctx, createdAt)
|
||||
}
|
||||
|
||||
// TODO: We need to create a ProvisionerJob resource type
|
||||
func (q *querier) GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.ProvisionerJob, error) {
|
||||
// if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
return q.db.GetProvisionerJobsCreatedAfter(ctx, createdAt)
|
||||
}
|
||||
|
||||
// Provisionerd server functions
|
||||
|
||||
func (q *querier) InsertWorkspaceAgent(ctx context.Context, arg database.InsertWorkspaceAgentParams) (database.WorkspaceAgent, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return database.WorkspaceAgent{}, err
|
||||
}
|
||||
return q.db.InsertWorkspaceAgent(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertWorkspaceApp(ctx context.Context, arg database.InsertWorkspaceAppParams) (database.WorkspaceApp, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return database.WorkspaceApp{}, err
|
||||
}
|
||||
return q.db.InsertWorkspaceApp(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertWorkspaceResourceMetadata(ctx context.Context, arg database.InsertWorkspaceResourceMetadataParams) ([]database.WorkspaceResourceMetadatum, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.InsertWorkspaceResourceMetadata(ctx, arg)
|
||||
}
|
||||
|
||||
// TODO: We need to create a ProvisionerJob resource type
|
||||
func (q *querier) AcquireProvisionerJob(ctx context.Context, arg database.AcquireProvisionerJobParams) (database.ProvisionerJob, error) {
|
||||
// if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
// return database.ProvisionerJob{}, err
|
||||
// }
|
||||
return q.db.AcquireProvisionerJob(ctx, arg)
|
||||
}
|
||||
|
||||
// TODO: We need to create a ProvisionerJob resource type
|
||||
func (q *querier) UpdateProvisionerJobWithCompleteByID(ctx context.Context, arg database.UpdateProvisionerJobWithCompleteByIDParams) error {
|
||||
// if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
// return err
|
||||
// }
|
||||
return q.db.UpdateProvisionerJobWithCompleteByID(ctx, arg)
|
||||
}
|
||||
|
||||
// TODO: We need to create a ProvisionerJob resource type
|
||||
func (q *querier) UpdateProvisionerJobByID(ctx context.Context, arg database.UpdateProvisionerJobByIDParams) error {
|
||||
// if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
// return err
|
||||
// }
|
||||
return q.db.UpdateProvisionerJobByID(ctx, arg)
|
||||
}
|
||||
|
||||
// TODO: We need to create a ProvisionerJob resource type
|
||||
func (q *querier) InsertProvisionerJob(ctx context.Context, arg database.InsertProvisionerJobParams) (database.ProvisionerJob, error) {
|
||||
// if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
// return database.ProvisionerJob{}, err
|
||||
// }
|
||||
return q.db.InsertProvisionerJob(ctx, arg)
|
||||
}
|
||||
|
||||
// TODO: We need to create a ProvisionerJob resource type
|
||||
func (q *querier) InsertProvisionerJobLogs(ctx context.Context, arg database.InsertProvisionerJobLogsParams) ([]database.ProvisionerJobLog, error) {
|
||||
// if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
return q.db.InsertProvisionerJobLogs(ctx, arg)
|
||||
}
|
||||
|
||||
// TODO: We need to create a ProvisionerDaemon resource type
|
||||
func (q *querier) InsertProvisionerDaemon(ctx context.Context, arg database.InsertProvisionerDaemonParams) (database.ProvisionerDaemon, error) {
|
||||
// if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
// return database.ProvisionerDaemon{}, err
|
||||
// }
|
||||
return q.db.InsertProvisionerDaemon(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertTemplateVersionParameter(ctx context.Context, arg database.InsertTemplateVersionParameterParams) (database.TemplateVersionParameter, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return database.TemplateVersionParameter{}, err
|
||||
}
|
||||
return q.db.InsertTemplateVersionParameter(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertTemplateVersionVariable(ctx context.Context, arg database.InsertTemplateVersionVariableParams) (database.TemplateVersionVariable, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return database.TemplateVersionVariable{}, err
|
||||
}
|
||||
return q.db.InsertTemplateVersionVariable(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertWorkspaceResource(ctx context.Context, arg database.InsertWorkspaceResourceParams) (database.WorkspaceResource, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return database.WorkspaceResource{}, err
|
||||
}
|
||||
return q.db.InsertWorkspaceResource(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertParameterSchema(ctx context.Context, arg database.InsertParameterSchemaParams) (database.ParameterSchema, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return database.ParameterSchema{}, err
|
||||
}
|
||||
return q.db.InsertParameterSchema(ctx, arg)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbgen"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/coderd/util/slice"
|
||||
)
|
||||
|
||||
func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
@@ -20,51 +22,51 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
UserID: u.ID,
|
||||
LinkedID: l.LinkedID,
|
||||
LoginType: database.LoginTypeGithub,
|
||||
}).Asserts().Returns(l)
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionUpdate).Returns(l)
|
||||
}))
|
||||
s.Run("GetUserLinkByLinkedID", s.Subtest(func(db database.Store, check *expects) {
|
||||
l := dbgen.UserLink(s.T(), db, database.UserLink{})
|
||||
check.Args(l.LinkedID).Asserts().Returns(l)
|
||||
check.Args(l.LinkedID).Asserts(rbac.ResourceSystem, rbac.ActionRead).Returns(l)
|
||||
}))
|
||||
s.Run("GetUserLinkByUserIDLoginType", s.Subtest(func(db database.Store, check *expects) {
|
||||
l := dbgen.UserLink(s.T(), db, database.UserLink{})
|
||||
check.Args(database.GetUserLinkByUserIDLoginTypeParams{
|
||||
UserID: l.UserID,
|
||||
LoginType: l.LoginType,
|
||||
}).Asserts().Returns(l)
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionRead).Returns(l)
|
||||
}))
|
||||
s.Run("GetLatestWorkspaceBuilds", s.Subtest(func(db database.Store, check *expects) {
|
||||
dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{})
|
||||
dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{})
|
||||
check.Args().Asserts()
|
||||
check.Args().Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetWorkspaceAgentByAuthToken", s.Subtest(func(db database.Store, check *expects) {
|
||||
agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{})
|
||||
check.Args(agt.AuthToken).Asserts().Returns(agt)
|
||||
check.Args(agt.AuthToken).Asserts(rbac.ResourceSystem, rbac.ActionRead).Returns(agt)
|
||||
}))
|
||||
s.Run("GetActiveUserCount", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args().Asserts().Returns(int64(0))
|
||||
check.Args().Asserts(rbac.ResourceSystem, rbac.ActionRead).Returns(int64(0))
|
||||
}))
|
||||
s.Run("GetUnexpiredLicenses", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args().Asserts()
|
||||
check.Args().Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetAuthorizationUserRoles", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
check.Args(u.ID).Asserts()
|
||||
check.Args(u.ID).Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetDERPMeshKey", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args().Asserts()
|
||||
check.Args().Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("InsertDERPMeshKey", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args("value").Asserts().Returns()
|
||||
check.Args("value").Asserts(rbac.ResourceSystem, rbac.ActionCreate).Returns()
|
||||
}))
|
||||
s.Run("InsertDeploymentID", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args("value").Asserts().Returns()
|
||||
check.Args("value").Asserts(rbac.ResourceSystem, rbac.ActionCreate).Returns()
|
||||
}))
|
||||
s.Run("InsertReplica", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.InsertReplicaParams{
|
||||
ID: uuid.New(),
|
||||
}).Asserts()
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionCreate)
|
||||
}))
|
||||
s.Run("UpdateReplica", s.Subtest(func(db database.Store, check *expects) {
|
||||
replica, err := db.InsertReplica(context.Background(), database.InsertReplicaParams{ID: uuid.New()})
|
||||
@@ -72,24 +74,24 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
check.Args(database.UpdateReplicaParams{
|
||||
ID: replica.ID,
|
||||
DatabaseLatency: 100,
|
||||
}).Asserts()
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionUpdate)
|
||||
}))
|
||||
s.Run("DeleteReplicasUpdatedBefore", s.Subtest(func(db database.Store, check *expects) {
|
||||
_, err := db.InsertReplica(context.Background(), database.InsertReplicaParams{ID: uuid.New(), UpdatedAt: time.Now()})
|
||||
require.NoError(s.T(), err)
|
||||
check.Args(time.Now().Add(time.Hour)).Asserts()
|
||||
check.Args(time.Now().Add(time.Hour)).Asserts(rbac.ResourceSystem, rbac.ActionDelete)
|
||||
}))
|
||||
s.Run("GetReplicasUpdatedAfter", s.Subtest(func(db database.Store, check *expects) {
|
||||
_, err := db.InsertReplica(context.Background(), database.InsertReplicaParams{ID: uuid.New(), UpdatedAt: time.Now()})
|
||||
require.NoError(s.T(), err)
|
||||
check.Args(time.Now().Add(time.Hour * -1)).Asserts()
|
||||
check.Args(time.Now().Add(time.Hour*-1)).Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetUserCount", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args().Asserts().Returns(int64(0))
|
||||
check.Args().Asserts(rbac.ResourceSystem, rbac.ActionRead).Returns(int64(0))
|
||||
}))
|
||||
s.Run("GetTemplates", s.Subtest(func(db database.Store, check *expects) {
|
||||
_ = dbgen.Template(s.T(), db, database.Template{})
|
||||
check.Args().Asserts()
|
||||
check.Args().Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("UpdateWorkspaceBuildCostByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{})
|
||||
@@ -98,115 +100,193 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
check.Args(database.UpdateWorkspaceBuildCostByIDParams{
|
||||
ID: b.ID,
|
||||
DailyCost: 10,
|
||||
}).Asserts().Returns(o)
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionUpdate).Returns(o)
|
||||
}))
|
||||
s.Run("InsertOrUpdateLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args("value").Asserts()
|
||||
check.Args("value").Asserts(rbac.ResourceSystem, rbac.ActionUpdate)
|
||||
}))
|
||||
s.Run("GetLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) {
|
||||
err := db.InsertOrUpdateLastUpdateCheck(context.Background(), "value")
|
||||
require.NoError(s.T(), err)
|
||||
check.Args().Asserts()
|
||||
check.Args().Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetWorkspaceBuildsCreatedAfter", s.Subtest(func(db database.Store, check *expects) {
|
||||
_ = dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{CreatedAt: time.Now().Add(-time.Hour)})
|
||||
check.Args(time.Now()).Asserts()
|
||||
check.Args(time.Now()).Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetWorkspaceAgentsCreatedAfter", s.Subtest(func(db database.Store, check *expects) {
|
||||
_ = dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{CreatedAt: time.Now().Add(-time.Hour)})
|
||||
check.Args(time.Now()).Asserts()
|
||||
check.Args(time.Now()).Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetWorkspaceAppsCreatedAfter", s.Subtest(func(db database.Store, check *expects) {
|
||||
_ = dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{CreatedAt: time.Now().Add(-time.Hour)})
|
||||
check.Args(time.Now()).Asserts()
|
||||
check.Args(time.Now()).Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetWorkspaceResourcesCreatedAfter", s.Subtest(func(db database.Store, check *expects) {
|
||||
_ = dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{CreatedAt: time.Now().Add(-time.Hour)})
|
||||
check.Args(time.Now()).Asserts()
|
||||
check.Args(time.Now()).Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetWorkspaceResourceMetadataCreatedAfter", s.Subtest(func(db database.Store, check *expects) {
|
||||
_ = dbgen.WorkspaceResourceMetadatums(s.T(), db, database.WorkspaceResourceMetadatum{})
|
||||
check.Args(time.Now()).Asserts()
|
||||
check.Args(time.Now()).Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("DeleteOldWorkspaceAgentStats", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args().Asserts()
|
||||
check.Args().Asserts(rbac.ResourceSystem, rbac.ActionDelete)
|
||||
}))
|
||||
s.Run("GetParameterSchemasCreatedAfter", s.Subtest(func(db database.Store, check *expects) {
|
||||
_ = dbgen.ParameterSchema(s.T(), db, database.ParameterSchema{CreatedAt: time.Now().Add(-time.Hour)})
|
||||
check.Args(time.Now()).Asserts()
|
||||
check.Args(time.Now()).Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetProvisionerJobsCreatedAfter", s.Subtest(func(db database.Store, check *expects) {
|
||||
// TODO: add provisioner job resource type
|
||||
_ = dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{CreatedAt: time.Now().Add(-time.Hour)})
|
||||
check.Args(time.Now()).Asserts()
|
||||
check.Args(time.Now()).Asserts( /*rbac.ResourceSystem, rbac.ActionRead*/ )
|
||||
}))
|
||||
s.Run("GetTemplateVersionsByIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
t1 := dbgen.Template(s.T(), db, database.Template{})
|
||||
t2 := dbgen.Template(s.T(), db, database.Template{})
|
||||
tv1 := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: t1.ID, Valid: true},
|
||||
})
|
||||
tv2 := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: t2.ID, Valid: true},
|
||||
})
|
||||
tv3 := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: t2.ID, Valid: true},
|
||||
})
|
||||
check.Args([]uuid.UUID{tv1.ID, tv2.ID, tv3.ID}).
|
||||
Asserts(rbac.ResourceSystem, rbac.ActionRead).
|
||||
Returns(slice.New(tv1, tv2, tv3))
|
||||
}))
|
||||
s.Run("GetWorkspaceAppsByAgentIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
aWs := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
aBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: aWs.ID, JobID: uuid.New()})
|
||||
aRes := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: aBuild.JobID})
|
||||
aAgt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: aRes.ID})
|
||||
a := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: aAgt.ID})
|
||||
|
||||
bWs := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
bBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: bWs.ID, JobID: uuid.New()})
|
||||
bRes := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: bBuild.JobID})
|
||||
bAgt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: bRes.ID})
|
||||
b := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: bAgt.ID})
|
||||
|
||||
check.Args([]uuid.UUID{a.AgentID, b.AgentID}).
|
||||
Asserts(rbac.ResourceSystem, rbac.ActionRead).
|
||||
Returns([]database.WorkspaceApp{a, b})
|
||||
}))
|
||||
s.Run("GetWorkspaceResourcesByJobIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
tpl := dbgen.Template(s.T(), db, database.Template{})
|
||||
v := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true}, JobID: uuid.New()})
|
||||
tJob := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: v.JobID, Type: database.ProvisionerJobTypeTemplateVersionImport})
|
||||
|
||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
||||
wJob := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild})
|
||||
check.Args([]uuid.UUID{tJob.ID, wJob.ID}).
|
||||
Asserts(rbac.ResourceSystem, rbac.ActionRead).
|
||||
Returns([]database.WorkspaceResource{})
|
||||
}))
|
||||
s.Run("GetWorkspaceResourceMetadataByResourceIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
||||
_ = dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild})
|
||||
a := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
|
||||
b := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
|
||||
check.Args([]uuid.UUID{a.ID, b.ID}).
|
||||
Asserts(rbac.ResourceSystem, rbac.ActionRead)
|
||||
}))
|
||||
s.Run("GetWorkspaceAgentsByResourceIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
||||
res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
|
||||
agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID})
|
||||
check.Args([]uuid.UUID{res.ID}).
|
||||
Asserts(rbac.ResourceSystem, rbac.ActionRead).
|
||||
Returns([]database.WorkspaceAgent{agt})
|
||||
}))
|
||||
s.Run("GetProvisionerJobsByIDs", s.Subtest(func(db database.Store, check *expects) {
|
||||
// TODO: add a ProvisionerJob resource type
|
||||
a := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{})
|
||||
b := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{})
|
||||
check.Args([]uuid.UUID{a.ID, b.ID}).
|
||||
Asserts( /*rbac.ResourceSystem, rbac.ActionRead*/ ).
|
||||
Returns(slice.New(a, b))
|
||||
}))
|
||||
s.Run("InsertWorkspaceAgent", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.InsertWorkspaceAgentParams{
|
||||
ID: uuid.New(),
|
||||
}).Asserts()
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionCreate)
|
||||
}))
|
||||
s.Run("InsertWorkspaceApp", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.InsertWorkspaceAppParams{
|
||||
ID: uuid.New(),
|
||||
Health: database.WorkspaceAppHealthDisabled,
|
||||
SharingLevel: database.AppSharingLevelOwner,
|
||||
}).Asserts()
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionCreate)
|
||||
}))
|
||||
s.Run("InsertWorkspaceResourceMetadata", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.InsertWorkspaceResourceMetadataParams{
|
||||
WorkspaceResourceID: uuid.New(),
|
||||
}).Asserts()
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionCreate)
|
||||
}))
|
||||
s.Run("AcquireProvisionerJob", s.Subtest(func(db database.Store, check *expects) {
|
||||
// TODO: we need to create a ProvisionerJob resource
|
||||
j := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{
|
||||
StartedAt: sql.NullTime{Valid: false},
|
||||
})
|
||||
check.Args(database.AcquireProvisionerJobParams{Types: []database.ProvisionerType{j.Provisioner}}).
|
||||
Asserts()
|
||||
Asserts( /*rbac.ResourceSystem, rbac.ActionUpdate*/ )
|
||||
}))
|
||||
s.Run("UpdateProvisionerJobWithCompleteByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
// TODO: we need to create a ProvisionerJob resource
|
||||
j := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{})
|
||||
check.Args(database.UpdateProvisionerJobWithCompleteByIDParams{
|
||||
ID: j.ID,
|
||||
}).Asserts()
|
||||
}).Asserts( /*rbac.ResourceSystem, rbac.ActionUpdate*/ )
|
||||
}))
|
||||
s.Run("UpdateProvisionerJobByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
// TODO: we need to create a ProvisionerJob resource
|
||||
j := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{})
|
||||
check.Args(database.UpdateProvisionerJobByIDParams{
|
||||
ID: j.ID,
|
||||
UpdatedAt: time.Now(),
|
||||
}).Asserts()
|
||||
}).Asserts( /*rbac.ResourceSystem, rbac.ActionUpdate*/ )
|
||||
}))
|
||||
s.Run("InsertProvisionerJob", s.Subtest(func(db database.Store, check *expects) {
|
||||
// TODO: we need to create a ProvisionerJob resource
|
||||
check.Args(database.InsertProvisionerJobParams{
|
||||
ID: uuid.New(),
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
}).Asserts()
|
||||
}).Asserts( /*rbac.ResourceSystem, rbac.ActionCreate*/ )
|
||||
}))
|
||||
s.Run("InsertProvisionerJobLogs", s.Subtest(func(db database.Store, check *expects) {
|
||||
// TODO: we need to create a ProvisionerJob resource
|
||||
j := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{})
|
||||
check.Args(database.InsertProvisionerJobLogsParams{
|
||||
JobID: j.ID,
|
||||
}).Asserts()
|
||||
}).Asserts( /*rbac.ResourceSystem, rbac.ActionCreate*/ )
|
||||
}))
|
||||
s.Run("InsertProvisionerDaemon", s.Subtest(func(db database.Store, check *expects) {
|
||||
// TODO: we need to create a ProvisionerDaemon resource
|
||||
check.Args(database.InsertProvisionerDaemonParams{
|
||||
ID: uuid.New(),
|
||||
}).Asserts()
|
||||
}).Asserts( /*rbac.ResourceSystem, rbac.ActionCreate*/ )
|
||||
}))
|
||||
s.Run("InsertTemplateVersionParameter", s.Subtest(func(db database.Store, check *expects) {
|
||||
v := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{})
|
||||
check.Args(database.InsertTemplateVersionParameterParams{
|
||||
TemplateVersionID: v.ID,
|
||||
}).Asserts()
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionCreate)
|
||||
}))
|
||||
s.Run("InsertWorkspaceResource", s.Subtest(func(db database.Store, check *expects) {
|
||||
r := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{})
|
||||
check.Args(database.InsertWorkspaceResourceParams{
|
||||
ID: r.ID,
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
}).Asserts()
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionCreate)
|
||||
}))
|
||||
s.Run("InsertParameterSchema", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.InsertParameterSchemaParams{
|
||||
@@ -214,6 +294,6 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
DefaultSourceScheme: database.ParameterSourceSchemeNone,
|
||||
DefaultDestinationScheme: database.ParameterDestinationSchemeNone,
|
||||
ValidationTypeSystem: database.ParameterTypeSystemNone,
|
||||
}).Asserts()
|
||||
}).Asserts(rbac.ResourceSystem, rbac.ActionCreate)
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -201,7 +201,9 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request,
|
||||
})
|
||||
return
|
||||
}
|
||||
resources, err := api.Database.GetWorkspaceResourcesByJobID(ctx, job.ID)
|
||||
|
||||
// nolint:gocritic // GetWorkspaceResourcesByJobID is a system function.
|
||||
resources, err := api.Database.GetWorkspaceResourcesByJobID(dbauthz.AsSystemRestricted(ctx), job.ID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
@@ -216,7 +218,9 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request,
|
||||
for _, resource := range resources {
|
||||
resourceIDs = append(resourceIDs, resource.ID)
|
||||
}
|
||||
resourceAgents, err := api.Database.GetWorkspaceAgentsByResourceIDs(ctx, resourceIDs)
|
||||
|
||||
// nolint:gocritic // GetWorkspaceAgentsByResourceIDs is a system function.
|
||||
resourceAgents, err := api.Database.GetWorkspaceAgentsByResourceIDs(dbauthz.AsSystemRestricted(ctx), resourceIDs)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
@@ -231,6 +235,8 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request,
|
||||
for _, agent := range resourceAgents {
|
||||
resourceAgentIDs = append(resourceAgentIDs, agent.ID)
|
||||
}
|
||||
|
||||
// nolint:gocritic // GetWorkspaceAppsByAgentIDs is a system function.
|
||||
apps, err := api.Database.GetWorkspaceAppsByAgentIDs(ctx, resourceAgentIDs)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
@@ -242,7 +248,9 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request,
|
||||
})
|
||||
return
|
||||
}
|
||||
resourceMetadata, err := api.Database.GetWorkspaceResourceMetadataByResourceIDs(ctx, resourceIDs)
|
||||
|
||||
// nolint:gocritic // GetWorkspaceResourceMetadataByResourceIDs is a system function.
|
||||
resourceMetadata, err := api.Database.GetWorkspaceResourceMetadataByResourceIDs(dbauthz.AsSystemRestricted(ctx), resourceIDs)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching workspace metadata.",
|
||||
|
||||
@@ -159,6 +159,11 @@ var (
|
||||
ResourceDebugInfo = Object{
|
||||
Type: "debug_info",
|
||||
}
|
||||
|
||||
// ResourceSystem is a pseudo-resource only used for system-level actions.
|
||||
ResourceSystem = Object{
|
||||
Type: "system",
|
||||
}
|
||||
)
|
||||
|
||||
// Object is used to create objects for authz checks when you have none in
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"cdr.dev/slog"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbauthz"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -209,7 +210,8 @@ func (c *Checker) update() (r Result, err error) {
|
||||
return r, xerrors.Errorf("json marshal result: %w", err)
|
||||
}
|
||||
|
||||
err = c.db.InsertOrUpdateLastUpdateCheck(ctx, string(b))
|
||||
// nolint:gocritic // Inserting the last update check is a system function.
|
||||
err = c.db.InsertOrUpdateLastUpdateCheck(dbauthz.AsSystemRestricted(ctx), string(b))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
@@ -224,7 +226,8 @@ func (c *Checker) notifyIfNewer(prev, next Result) {
|
||||
}
|
||||
|
||||
func (c *Checker) lastUpdateCheck(ctx context.Context) (r Result, err error) {
|
||||
s, err := c.db.GetLastUpdateCheck(ctx)
|
||||
// nolint:gocritic // Getting the last update check is a system function.
|
||||
s, err := c.db.GetLastUpdateCheck(dbauthz.AsSystemRestricted(ctx))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
+4
-3
@@ -37,7 +37,8 @@ import (
|
||||
// @Router /users/first [get]
|
||||
func (api *API) firstUser(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
userCount, err := api.Database.GetUserCount(ctx)
|
||||
// nolint:gocritic // Getting user count is a system function.
|
||||
userCount, err := api.Database.GetUserCount(dbauthz.AsSystemRestricted(ctx))
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user count.",
|
||||
@@ -70,7 +71,6 @@ func (api *API) firstUser(rw http.ResponseWriter, r *http.Request) {
|
||||
// @Success 201 {object} codersdk.CreateFirstUserResponse
|
||||
// @Router /users/first [post]
|
||||
func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
||||
// TODO: Should this admin system context be in a middleware?
|
||||
ctx := r.Context()
|
||||
var createUser codersdk.CreateFirstUserRequest
|
||||
if !httpapi.Read(ctx, rw, r, &createUser) {
|
||||
@@ -78,7 +78,8 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// This should only function for the first user.
|
||||
userCount, err := api.Database.GetUserCount(ctx)
|
||||
// nolint:gocritic // Getting user count is a system function.
|
||||
userCount, err := api.Database.GetUserCount(dbauthz.AsSystemRestricted(ctx))
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user count.",
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/coderd/httpmw"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
@@ -38,6 +39,12 @@ const (
|
||||
//
|
||||
// Upstream code should avoid any database calls ever.
|
||||
func (p *Provider) ResolveRequest(rw http.ResponseWriter, r *http.Request, appReq Request) (*Ticket, bool) {
|
||||
// nolint:gocritic // We need to make a number of database calls. Setting a system context here
|
||||
// // is simpler than calling dbauthz.AsSystemRestricted on every call.
|
||||
// // dangerousSystemCtx is only used for database calls. The actual authentication
|
||||
// // logic is handled in Provider.authorizeWorkspaceApp which directly checks the actor's
|
||||
// // permissions.
|
||||
dangerousSystemCtx := dbauthz.AsSystemRestricted(r.Context())
|
||||
err := appReq.Validate()
|
||||
if err != nil {
|
||||
p.writeWorkspaceApp500(rw, r, &appReq, err, "invalid app request")
|
||||
@@ -108,9 +115,9 @@ func (p *Provider) ResolveRequest(rw http.ResponseWriter, r *http.Request, appRe
|
||||
userErr error
|
||||
)
|
||||
if userID, uuidErr := uuid.Parse(appReq.UsernameOrID); uuidErr == nil {
|
||||
user, userErr = p.Database.GetUserByID(r.Context(), userID)
|
||||
user, userErr = p.Database.GetUserByID(dangerousSystemCtx, userID)
|
||||
} else {
|
||||
user, userErr = p.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
|
||||
user, userErr = p.Database.GetUserByEmailOrUsername(dangerousSystemCtx, database.GetUserByEmailOrUsernameParams{
|
||||
Username: appReq.UsernameOrID,
|
||||
})
|
||||
}
|
||||
@@ -129,9 +136,9 @@ func (p *Provider) ResolveRequest(rw http.ResponseWriter, r *http.Request, appRe
|
||||
workspaceErr error
|
||||
)
|
||||
if workspaceID, uuidErr := uuid.Parse(appReq.WorkspaceNameOrID); uuidErr == nil {
|
||||
workspace, workspaceErr = p.Database.GetWorkspaceByID(r.Context(), workspaceID)
|
||||
workspace, workspaceErr = p.Database.GetWorkspaceByID(dangerousSystemCtx, workspaceID)
|
||||
} else {
|
||||
workspace, workspaceErr = p.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
|
||||
workspace, workspaceErr = p.Database.GetWorkspaceByOwnerIDAndName(dangerousSystemCtx, database.GetWorkspaceByOwnerIDAndNameParams{
|
||||
OwnerID: user.ID,
|
||||
Name: appReq.WorkspaceNameOrID,
|
||||
Deleted: false,
|
||||
@@ -153,15 +160,16 @@ func (p *Provider) ResolveRequest(rw http.ResponseWriter, r *http.Request, appRe
|
||||
trustAgent = false
|
||||
)
|
||||
if agentID, uuidErr := uuid.Parse(appReq.AgentNameOrID); uuidErr == nil {
|
||||
agent, agentErr = p.Database.GetWorkspaceAgentByID(r.Context(), agentID)
|
||||
agent, agentErr = p.Database.GetWorkspaceAgentByID(dangerousSystemCtx, agentID)
|
||||
} else {
|
||||
build, err := p.Database.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID)
|
||||
build, err := p.Database.GetLatestWorkspaceBuildByWorkspaceID(dangerousSystemCtx, workspace.ID)
|
||||
if err != nil {
|
||||
p.writeWorkspaceApp500(rw, r, &appReq, err, "get latest workspace build")
|
||||
return nil, false
|
||||
}
|
||||
|
||||
resources, err := p.Database.GetWorkspaceResourcesByJobID(r.Context(), build.JobID)
|
||||
// nolint:gocritic // We need to fetch the agent to authenticate the request. This is a system function.
|
||||
resources, err := p.Database.GetWorkspaceResourcesByJobID(dangerousSystemCtx, build.JobID)
|
||||
if err != nil {
|
||||
p.writeWorkspaceApp500(rw, r, &appReq, err, "get workspace resources")
|
||||
return nil, false
|
||||
@@ -171,7 +179,8 @@ func (p *Provider) ResolveRequest(rw http.ResponseWriter, r *http.Request, appRe
|
||||
resourcesIDs = append(resourcesIDs, resource.ID)
|
||||
}
|
||||
|
||||
agents, err := p.Database.GetWorkspaceAgentsByResourceIDs(r.Context(), resourcesIDs)
|
||||
// nolint:gocritic // We need to fetch the agent to authenticate the request. This is a system function.
|
||||
agents, err := p.Database.GetWorkspaceAgentsByResourceIDs(dangerousSystemCtx, resourcesIDs)
|
||||
if err != nil {
|
||||
p.writeWorkspaceApp500(rw, r, &appReq, err, "get workspace agents")
|
||||
return nil, false
|
||||
@@ -209,12 +218,13 @@ func (p *Provider) ResolveRequest(rw http.ResponseWriter, r *http.Request, appRe
|
||||
|
||||
// Verify the agent belongs to the workspace.
|
||||
if !trustAgent {
|
||||
agentResource, err := p.Database.GetWorkspaceResourceByID(r.Context(), agent.ResourceID)
|
||||
//nolint:gocritic // We need to fetch the agent to authenticate the request. This is a system function.
|
||||
agentResource, err := p.Database.GetWorkspaceResourceByID(dangerousSystemCtx, agent.ResourceID)
|
||||
if err != nil {
|
||||
p.writeWorkspaceApp500(rw, r, &appReq, err, "get agent resource")
|
||||
return nil, false
|
||||
}
|
||||
build, err := p.Database.GetWorkspaceBuildByJobID(r.Context(), agentResource.JobID)
|
||||
build, err := p.Database.GetWorkspaceBuildByJobID(dangerousSystemCtx, agentResource.JobID)
|
||||
if err != nil {
|
||||
p.writeWorkspaceApp500(rw, r, &appReq, err, "get agent workspace build")
|
||||
return nil, false
|
||||
@@ -324,7 +334,8 @@ func (p *Provider) ResolveRequest(rw http.ResponseWriter, r *http.Request, appRe
|
||||
// error while looking it up, an HTML error page is returned and false is
|
||||
// returned so the caller can return early.
|
||||
func (p *Provider) lookupWorkspaceApp(rw http.ResponseWriter, r *http.Request, agentID uuid.UUID, appSlug string) (database.WorkspaceApp, bool) {
|
||||
app, err := p.Database.GetWorkspaceAppByAgentIDAndSlug(r.Context(), database.GetWorkspaceAppByAgentIDAndSlugParams{
|
||||
// nolint:gocritic // We need to fetch the workspace app to authorize the request.
|
||||
app, err := p.Database.GetWorkspaceAppByAgentIDAndSlug(dbauthz.AsSystemRestricted(r.Context()), database.GetWorkspaceAppByAgentIDAndSlugParams{
|
||||
AgentID: agentID,
|
||||
Slug: appSlug,
|
||||
})
|
||||
|
||||
@@ -540,6 +540,26 @@ func Test_ResolveRequest(t *testing.T) {
|
||||
require.Nil(t, ticket)
|
||||
})
|
||||
|
||||
t.Run("UserNotFound", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
req := workspaceapps.Request{
|
||||
AccessMethod: workspaceapps.AccessMethodPath,
|
||||
BasePath: "/app",
|
||||
UsernameOrID: "thisuserdoesnotexist",
|
||||
WorkspaceNameOrID: workspace.Name,
|
||||
AgentNameOrID: agentName,
|
||||
AppSlugOrPort: appNameOwner,
|
||||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/app", nil)
|
||||
r.Header.Set(codersdk.SessionTokenHeader, client.SessionToken())
|
||||
|
||||
ticket, ok := api.WorkspaceAppsProvider.ResolveRequest(rw, r, req)
|
||||
require.False(t, ok)
|
||||
require.Nil(t, ticket)
|
||||
})
|
||||
|
||||
t.Run("RedirectSubdomainAuth", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1309,6 +1309,8 @@ func TestAppSharing(t *testing.T) {
|
||||
// Verify that the apps have the correct sharing levels set.
|
||||
workspaceBuild, err := client.WorkspaceBuild(ctx, workspace.LatestBuild.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, workspaceBuild.Resources, "workspace build has no resources")
|
||||
require.NotEmpty(t, workspaceBuild.Resources[0].Agents, "workspace build has no agents")
|
||||
agnt = workspaceBuild.Resources[0].Agents[0]
|
||||
found := map[string]codersdk.WorkspaceAppSharingLevel{}
|
||||
expected := map[string]codersdk.WorkspaceAppSharingLevel{
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/coderd/httpmw"
|
||||
"github.com/coder/coder/coderd/provisionerdserver"
|
||||
@@ -50,6 +51,24 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure we have the job and template version for the workspace build.
|
||||
// Otherwise we risk a panic in the api.convertWorkspaceBuild call below.
|
||||
if len(data.jobs) == 0 {
|
||||
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
|
||||
Message: "Internal error getting workspace build data.",
|
||||
Detail: "No job found for workspace build.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if len(data.templateVersions) == 0 {
|
||||
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
|
||||
Message: "Internal error getting workspace build data.",
|
||||
Detail: "No template version found for workspace build.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
apiBuild, err := api.convertWorkspaceBuild(
|
||||
workspaceBuild,
|
||||
workspace,
|
||||
@@ -949,12 +968,15 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.W
|
||||
for _, build := range workspaceBuilds {
|
||||
templateVersionIDs = append(templateVersionIDs, build.TemplateVersionID)
|
||||
}
|
||||
templateVersions, err := api.Database.GetTemplateVersionsByIDs(ctx, templateVersionIDs)
|
||||
|
||||
// nolint:gocritic // Getting template versions by ID is a system function.
|
||||
templateVersions, err := api.Database.GetTemplateVersionsByIDs(dbauthz.AsSystemRestricted(ctx), templateVersionIDs)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return workspaceBuildsData{}, xerrors.Errorf("get template versions: %w", err)
|
||||
}
|
||||
|
||||
resources, err := api.Database.GetWorkspaceResourcesByJobIDs(ctx, jobIDs)
|
||||
// nolint:gocritic // Getting workspace resources by job ID is a system function.
|
||||
resources, err := api.Database.GetWorkspaceResourcesByJobIDs(dbauthz.AsSystemRestricted(ctx), jobIDs)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return workspaceBuildsData{}, xerrors.Errorf("get workspace resources by job: %w", err)
|
||||
}
|
||||
@@ -972,12 +994,14 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.W
|
||||
resourceIDs = append(resourceIDs, resource.ID)
|
||||
}
|
||||
|
||||
metadata, err := api.Database.GetWorkspaceResourceMetadataByResourceIDs(ctx, resourceIDs)
|
||||
// nolint:gocritic // Getting workspace resource metadata by resource ID is a system function.
|
||||
metadata, err := api.Database.GetWorkspaceResourceMetadataByResourceIDs(dbauthz.AsSystemRestricted(ctx), resourceIDs)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return workspaceBuildsData{}, xerrors.Errorf("fetching resource metadata: %w", err)
|
||||
}
|
||||
|
||||
agents, err := api.Database.GetWorkspaceAgentsByResourceIDs(ctx, resourceIDs)
|
||||
// nolint:gocritic // Getting workspace agents by resource IDs is a system function.
|
||||
agents, err := api.Database.GetWorkspaceAgentsByResourceIDs(dbauthz.AsSystemRestricted(ctx), resourceIDs)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return workspaceBuildsData{}, xerrors.Errorf("get workspace agents: %w", err)
|
||||
}
|
||||
@@ -997,7 +1021,8 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.W
|
||||
agentIDs = append(agentIDs, agent.ID)
|
||||
}
|
||||
|
||||
apps, err := api.Database.GetWorkspaceAppsByAgentIDs(ctx, agentIDs)
|
||||
// nolint:gocritic // Getting workspace apps by agent IDs is a system function.
|
||||
apps, err := api.Database.GetWorkspaceAppsByAgentIDs(dbauthz.AsSystemRestricted(ctx), agentIDs)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return workspaceBuildsData{}, xerrors.Errorf("fetching workspace apps: %w", err)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"cdr.dev/slog"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
@@ -39,12 +40,14 @@ func Entitlements(
|
||||
}
|
||||
}
|
||||
|
||||
licenses, err := db.GetUnexpiredLicenses(ctx)
|
||||
// nolint:gocritic // Getting unexpired licenses is a system function.
|
||||
licenses, err := db.GetUnexpiredLicenses(dbauthz.AsSystemRestricted(ctx))
|
||||
if err != nil {
|
||||
return entitlements, err
|
||||
}
|
||||
|
||||
activeUserCount, err := db.GetActiveUserCount(ctx)
|
||||
// nolint:gocritic // Getting active user count is a system function.
|
||||
activeUserCount, err := db.GetActiveUserCount(dbauthz.AsSystemRestricted(ctx))
|
||||
if err != nil {
|
||||
return entitlements, xerrors.Errorf("query active user count: %w", err)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/coderd/provisionerdserver"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/enterprise/coderd/license"
|
||||
@@ -20,6 +21,22 @@ import (
|
||||
|
||||
func TestProvisionerDaemonServe(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdenttest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureExternalProvisionerDaemons: 1,
|
||||
},
|
||||
})
|
||||
srv, err := client.ServeProvisionerDaemon(context.Background(), user.OrganizationID, []codersdk.ProvisionerType{
|
||||
codersdk.ProvisionerTypeEcho,
|
||||
}, map[string]string{})
|
||||
require.NoError(t, err)
|
||||
srv.DRPCConn().Close()
|
||||
})
|
||||
|
||||
t.Run("NoLicense", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdenttest.New(t, nil)
|
||||
@@ -42,11 +59,16 @@ func TestProvisionerDaemonServe(t *testing.T) {
|
||||
codersdk.FeatureExternalProvisionerDaemons: 1,
|
||||
},
|
||||
})
|
||||
srv, err := client.ServeProvisionerDaemon(context.Background(), user.OrganizationID, []codersdk.ProvisionerType{
|
||||
another, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleOrgAdmin(user.OrganizationID))
|
||||
_, err := another.ServeProvisionerDaemon(context.Background(), user.OrganizationID, []codersdk.ProvisionerType{
|
||||
codersdk.ProvisionerTypeEcho,
|
||||
}, map[string]string{})
|
||||
require.NoError(t, err)
|
||||
srv.DRPCConn().Close()
|
||||
}, map[string]string{
|
||||
provisionerdserver.TagScope: provisionerdserver.ScopeOrganization,
|
||||
})
|
||||
require.Error(t, err)
|
||||
var apiError *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiError)
|
||||
require.Equal(t, http.StatusForbidden, apiError.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("OrganizationNoPerms", func(t *testing.T) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/coder/coder/buildinfo"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbauthz"
|
||||
)
|
||||
|
||||
var PubsubEvent = "replica"
|
||||
@@ -61,7 +62,8 @@ func New(ctx context.Context, logger slog.Logger, db database.Store, pubsub data
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("ping database: %w", err)
|
||||
}
|
||||
replica, err := db.InsertReplica(ctx, database.InsertReplicaParams{
|
||||
// nolint:gocritic // Inserting a replica is a system function.
|
||||
replica, err := db.InsertReplica(dbauthz.AsSystemRestricted(ctx), database.InsertReplicaParams{
|
||||
ID: options.ID,
|
||||
CreatedAt: database.Now(),
|
||||
StartedAt: database.Now(),
|
||||
@@ -141,7 +143,8 @@ func (m *Manager) loop(ctx context.Context) {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-deleteTicker.C:
|
||||
err := m.db.DeleteReplicasUpdatedBefore(ctx, m.updateInterval())
|
||||
// nolint:gocritic // Deleting a replica is a system function
|
||||
err := m.db.DeleteReplicasUpdatedBefore(dbauthz.AsSystemRestricted(ctx), m.updateInterval())
|
||||
if err != nil {
|
||||
m.logger.Warn(ctx, "delete old replicas", slog.Error(err))
|
||||
}
|
||||
@@ -218,7 +221,8 @@ func (m *Manager) syncReplicas(ctx context.Context) error {
|
||||
defer m.closeWait.Done()
|
||||
// Expect replicas to update once every three times the interval...
|
||||
// If they don't, assume death!
|
||||
replicas, err := m.db.GetReplicasUpdatedAfter(ctx, m.updateInterval())
|
||||
// nolint:gocritic // Reading replicas is a system function
|
||||
replicas, err := m.db.GetReplicasUpdatedAfter(dbauthz.AsSystemRestricted(ctx), m.updateInterval())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get replicas: %w", err)
|
||||
}
|
||||
@@ -276,7 +280,8 @@ func (m *Manager) syncReplicas(ctx context.Context) error {
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
replica, err := m.db.UpdateReplica(ctx, database.UpdateReplicaParams{
|
||||
// nolint:gocritic // Updating a replica is a system function.
|
||||
replica, err := m.db.UpdateReplica(dbauthz.AsSystemRestricted(ctx), database.UpdateReplicaParams{
|
||||
ID: m.self.ID,
|
||||
UpdatedAt: database.Now(),
|
||||
StartedAt: m.self.StartedAt,
|
||||
@@ -366,7 +371,8 @@ func (m *Manager) Close() error {
|
||||
defer m.mutex.Unlock()
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancelFunc()
|
||||
_, err := m.db.UpdateReplica(ctx, database.UpdateReplicaParams{
|
||||
// nolint:gocritic // Updating a replica is a sytsem function.
|
||||
_, err := m.db.UpdateReplica(dbauthz.AsSystemRestricted(ctx), database.UpdateReplicaParams{
|
||||
ID: m.self.ID,
|
||||
UpdatedAt: database.Now(),
|
||||
StartedAt: m.self.StartedAt,
|
||||
|
||||
Reference in New Issue
Block a user