Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

refactor: consolidate template and workspace acl validation#19192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
aslilac merged 16 commits intomainfromlilac/acl-update-validator
Aug 7, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
16 commits
Select commitHold shift + click to select a range
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
3 changes: 2 additions & 1 deletioncoderd/coderd.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1415,7 +1415,8 @@ func New(options *Options) *API {
r.Get("/timings", api.workspaceTimings)
r.Route("/acl", func(r chi.Router) {
r.Use(
httpmw.RequireExperiment(api.Experiments, codersdk.ExperimentWorkspaceSharing))
httpmw.RequireExperiment(api.Experiments, codersdk.ExperimentWorkspaceSharing),
)

r.Patch("/", api.patchWorkspaceACL)
})
Expand Down
20 changes: 20 additions & 0 deletionscoderd/database/dbauthz/dbauthz.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -5376,6 +5376,26 @@ func (q *querier) UpsertWorkspaceAppAuditSession(ctx context.Context, arg databa
return q.db.UpsertWorkspaceAppAuditSession(ctx, arg)
}

func (q *querier) ValidateGroupIDs(ctx context.Context, groupIDs []uuid.UUID) (database.ValidateGroupIDsRow, error) {
// This check is probably overly restrictive, but the "correct" check isn't
// necessarily obvious. It's only used as a verification check for ACLs right
// now, which are performed as system.
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
return database.ValidateGroupIDsRow{}, err
}
return q.db.ValidateGroupIDs(ctx, groupIDs)
}

func (q *querier) ValidateUserIDs(ctx context.Context, userIDs []uuid.UUID) (database.ValidateUserIDsRow, error) {
// This check is probably overly restrictive, but the "correct" check isn't
// necessarily obvious. It's only used as a verification check for ACLs right
// now, which are performed as system.
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
return database.ValidateUserIDsRow{}, err
}
return q.db.ValidateUserIDs(ctx, userIDs)
}

func (q *querier) GetAuthorizedTemplates(ctx context.Context, arg database.GetTemplatesWithFilterParams, _ rbac.PreparedAuthorized) ([]database.Template, error) {
// TODO Delete this function, all GetTemplates should be authorized. For now just call getTemplates on the authz querier.
return q.GetTemplatesWithFilter(ctx, arg)
Expand Down
9 changes: 9 additions & 0 deletionscoderd/database/dbauthz/dbauthz_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -623,6 +623,11 @@ func (s *MethodTestSuite) TestGroup() {
ID: g.ID,
}).Asserts(g, policy.ActionUpdate)
}))
s.Run("ValidateGroupIDs", s.Subtest(func(db database.Store, check *expects) {
o := dbgen.Organization(s.T(), db, database.Organization{})
g := dbgen.Group(s.T(), db, database.Group{OrganizationID: o.ID})
check.Args([]uuid.UUID{g.ID}).Asserts(rbac.ResourceSystem, policy.ActionRead)
}))
}

func (s *MethodTestSuite) TestProvisionerJob() {
Expand DownExpand Up@@ -2077,6 +2082,10 @@ func (s *MethodTestSuite) TestUser() {
Interval: int32((time.Hour * 24).Seconds()),
}).Asserts(rbac.ResourceUser, policy.ActionRead)
}))
s.Run("ValidateUserIDs", s.Subtest(func(db database.Store, check *expects) {
u := dbgen.User(s.T(), db, database.User{})
check.Args([]uuid.UUID{u.ID}).Asserts(rbac.ResourceSystem, policy.ActionRead)
}))
}

func (s *MethodTestSuite) TestWorkspace() {
Expand Down
14 changes: 14 additions & 0 deletionscoderd/database/dbmetrics/querymetrics.go
View file
Open in desktop

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

30 changes: 30 additions & 0 deletionscoderd/database/dbmock/dbmock.go
View file
Open in desktop

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

2 changes: 2 additions & 0 deletionscoderd/database/querier.go
View file
Open in desktop

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

64 changes: 64 additions & 0 deletionscoderd/database/queries.sql.go
View file
Open in desktop

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

18 changes: 18 additions & 0 deletionscoderd/database/queries/groups.sql
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,24 @@ WHERE
LIMIT
1;

-- name: ValidateGroupIDs :one
WITH input AS (
SELECT
unnest(@group_ids::uuid[]) AS id
)
SELECT
array_agg(input.id)::uuid[] as invalid_group_ids,
COUNT(*) = 0 as ok
FROM
-- Preserve rows where there is not a matching left (groups) row for each
-- right (input) row...
groups
RIGHT JOIN input ON groups.id = input.id
WHERE
-- ...so that we can retain exactly those rows where an input ID does not
-- match an existing group.
groups.id IS NULL;

-- name: GetGroupByOrgAndName :one
SELECT
*
Expand Down
20 changes: 20 additions & 0 deletionscoderd/database/queries/users.sql
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,26 @@ WHERE
LIMIT
1;

-- name: ValidateUserIDs :one
WITH input AS (
SELECT
unnest(@user_ids::uuid[]) AS id
)
SELECT
array_agg(input.id)::uuid[] as invalid_user_ids,
COUNT(*) = 0 as ok
FROM
-- Preserve rows where there is not a matching left (users) row for each
-- right (input) row...
users
RIGHT JOIN input ON users.id = input.id
WHERE
-- ...so that we can retain exactly those rows where an input ID does not
-- match an existing user...
users.id IS NULL OR
-- ...or that only matches a user that was deleted.
users.deleted = true;

-- name: GetUsersByIDs :many
-- This shouldn't check for deleted, because it's frequently used
-- to look up references to actions. eg. a user could build a workspace
Expand Down
130 changes: 130 additions & 0 deletionscoderd/rbac/acl/updatevalidator.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
package acl

import (
"context"
"fmt"

"github.com/google/uuid"

"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/codersdk"
)

typeUpdateValidator[Role codersdk.WorkspaceRole| codersdk.TemplateRole]interface {
// Users should return a map from user UUIDs (as strings) to the role they
// are being assigned. Additionally, it should return a string that will be
// used as the field name for the ValidationErrors returned from Validate.
Users() (map[string]Role,string)
// Groups should return a map from group UUIDs (as strings) to the role they
// are being assigned. Additionally, it should return a string that will be
// used as the field name for the ValidationErrors returned from Validate.
Groups() (map[string]Role,string)
// ValidateRole should return an error that will be used in the
// ValidationError if the role is invalid for the corresponding resource type.
ValidateRole(roleRole)error
}
Comment on lines +14 to +26
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

❤️


funcValidate[Role codersdk.WorkspaceRole| codersdk.TemplateRole](
ctx context.Context,
db database.Store,
vUpdateValidator[Role],
) []codersdk.ValidationError {
// nolint:gocritic // Validate requires full read access to users and groups
ctx=dbauthz.AsSystemRestricted(ctx)
varvalidErrs []codersdk.ValidationError

groupRoles,groupsField:=v.Groups()
groupIDs:=make([]uuid.UUID,0,len(groupRoles))
foridStr,role:=rangegroupRoles {
// Validate the provided role names
iferr:=v.ValidateRole(role);err!=nil {
validErrs=append(validErrs, codersdk.ValidationError{
Field:groupsField,
Detail:err.Error(),
})
}
// Validate that the IDs are UUIDs
id,err:=uuid.Parse(idStr)
iferr!=nil {
validErrs=append(validErrs, codersdk.ValidationError{
Field:groupsField,
Detail:fmt.Sprintf("%v is not a valid UUID.",idStr),
})
continue
}
// Don't check if the ID exists when setting the role to
// WorkspaceRoleDeleted or TemplateRoleDeleted. They might've existing at
// some point and got deleted. If we report that as an error here then they
// can't be removed.
ifstring(role)=="" {
continue
}
groupIDs=append(groupIDs,id)
}

// Validate that the groups exist
groupValidation,err:=db.ValidateGroupIDs(ctx,groupIDs)
iferr!=nil {
validErrs=append(validErrs, codersdk.ValidationError{
Field:groupsField,
Detail:fmt.Sprintf("failed to validate group IDs: %v",err.Error()),
})
}
if!groupValidation.Ok {
for_,id:=rangegroupValidation.InvalidGroupIds {
validErrs=append(validErrs, codersdk.ValidationError{
Field:groupsField,
Detail:fmt.Sprintf("group with ID %v does not exist",id),
})
}
}

userRoles,usersField:=v.Users()
userIDs:=make([]uuid.UUID,0,len(userRoles))
foridStr,role:=rangeuserRoles {
// Validate the provided role names
iferr:=v.ValidateRole(role);err!=nil {
validErrs=append(validErrs, codersdk.ValidationError{
Field:usersField,
Detail:err.Error(),
})
}
// Validate that the IDs are UUIDs
id,err:=uuid.Parse(idStr)
iferr!=nil {
validErrs=append(validErrs, codersdk.ValidationError{
Field:usersField,
Detail:fmt.Sprintf("%v is not a valid UUID.",idStr),
})
continue
}
// Don't check if the ID exists when setting the role to
// WorkspaceRoleDeleted or TemplateRoleDeleted. They might've existing at
// some point and got deleted. If we report that as an error here then they
// can't be removed.
ifstring(role)=="" {
continue
}
userIDs=append(userIDs,id)
}

// Validate that the groups exist
userValidation,err:=db.ValidateUserIDs(ctx,userIDs)
iferr!=nil {
validErrs=append(validErrs, codersdk.ValidationError{
Field:usersField,
Detail:fmt.Sprintf("failed to validate user IDs: %v",err.Error()),
})
}
if!userValidation.Ok {
for_,id:=rangeuserValidation.InvalidUserIds {
validErrs=append(validErrs, codersdk.ValidationError{
Field:usersField,
Detail:fmt.Sprintf("user with ID %v does not exist",id),
})
}
}

returnvalidErrs
}
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp