Skip to content

Commit e30250a

Browse files
committed
refactor: consolidate template and workspace acl validation
1 parent 4ba9382 commit e30250a

File tree

10 files changed

+314
-89
lines changed

10 files changed

+314
-89
lines changed

coderd/database/dbauthz/dbauthz.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5376,6 +5376,26 @@ func (q *querier) UpsertWorkspaceAppAuditSession(ctx context.Context, arg databa
53765376
return q.db.UpsertWorkspaceAppAuditSession(ctx, arg)
53775377
}
53785378

5379+
func (q *querier) ValidateGroupIDs(ctx context.Context, groupIds []uuid.UUID) (database.ValidateGroupIDsRow, error) {
5380+
// This check is probably overly restrictive, but the "correct" check isn't
5381+
// necessarily obvious. It's only used as a verification check for ACLs right
5382+
// now, which are performed as system.
5383+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
5384+
return database.ValidateGroupIDsRow{}, err
5385+
}
5386+
return q.db.ValidateGroupIDs(ctx, groupIds)
5387+
}
5388+
5389+
func (q *querier) ValidateUserIDs(ctx context.Context, userIds []uuid.UUID) (database.ValidateUserIDsRow, error) {
5390+
// This check is probably overly restrictive, but the "correct" check isn't
5391+
// necessarily obvious. It's only used as a verification check for ACLs right
5392+
// now, which are performed as system.
5393+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
5394+
return database.ValidateUserIDsRow{}, err
5395+
}
5396+
return q.db.ValidateUserIDs(ctx, userIds)
5397+
}
5398+
53795399
func (q *querier) GetAuthorizedTemplates(ctx context.Context, arg database.GetTemplatesWithFilterParams, _ rbac.PreparedAuthorized) ([]database.Template, error) {
53805400
// TODO Delete this function, all GetTemplates should be authorized. For now just call getTemplates on the authz querier.
53815401
return q.GetTemplatesWithFilter(ctx, arg)

coderd/database/dbmetrics/querymetrics.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbmock/dbmock.go

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/querier.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 63 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/groups.sql

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@ WHERE
88
LIMIT
99
1;
1010

11+
-- name: ValidateGroupIDs :one
12+
WITH input AS (
13+
SELECT
14+
unnest(@group_ids::uuid[]) AS id
15+
)
16+
SELECT
17+
array_agg(input.id)::uuid[] as invalid_group_ids,
18+
COUNT(*) = 0 as ok
19+
FROM
20+
-- Preserve rows where there is not a matching left (groups) row for each
21+
-- right (input) row...
22+
users
23+
RIGHT JOIN input ON groups.id = input.id
24+
WHERE
25+
-- ...so that we can retain exactly those rows where an input ID does not
26+
-- match an existing group.
27+
groups.id IS NOT DISTINCT FROM NULL;
28+
1129
-- name: GetGroupByOrgAndName :one
1230
SELECT
1331
*

coderd/database/queries/users.sql

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ WHERE
2525
LIMIT
2626
1;
2727

28+
-- name: ValidateUserIDs :one
29+
WITH input AS (
30+
SELECT
31+
unnest(@user_ids::uuid[]) AS id
32+
)
33+
SELECT
34+
array_agg(input.id)::uuid[] as invalid_user_ids,
35+
COUNT(*) = 0 as ok
36+
FROM
37+
-- Preserve rows where there is not a matching left (users) row for each
38+
-- right (input) row...
39+
users
40+
RIGHT JOIN input ON users.id = input.id
41+
WHERE
42+
-- ...so that we can retain exactly those rows where an input ID does not
43+
-- match an existing user.
44+
users.id IS NOT DISTINCT FROM NULL AND
45+
users.deleted = false;
46+
2847
-- name: GetUsersByIDs :many
2948
-- This shouldn't check for deleted, because it's frequently used
3049
-- to look up references to actions. eg. a user could build a workspace

coderd/rbac/acl/updatevalidator.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package acl
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/google/uuid"
8+
9+
"github.com/coder/coder/v2/coderd/database"
10+
"github.com/coder/coder/v2/coderd/database/dbauthz"
11+
"github.com/coder/coder/v2/codersdk"
12+
)
13+
14+
type ACLUpdateValidator[Role codersdk.WorkspaceRole | codersdk.TemplateRole] interface {
15+
Users() (map[string]Role, string)
16+
Groups() (map[string]Role, string)
17+
ValidateRole(role Role) error
18+
}
19+
20+
func Validate[T codersdk.WorkspaceRole | codersdk.TemplateRole](
21+
ctx context.Context,
22+
db database.Store,
23+
v ACLUpdateValidator[T],
24+
) []codersdk.ValidationError {
25+
// nolint:gocritic // Validate requires full read access to users and groups
26+
ctx = dbauthz.AsSystemRestricted(ctx)
27+
var validErrs []codersdk.ValidationError
28+
29+
groupPerms, groupsField := v.Groups()
30+
groupIDs := make([]uuid.UUID, 0, len(groupPerms))
31+
for idStr, role := range groupPerms {
32+
// Validate the provided role names
33+
if err := v.ValidateRole(role); err != nil {
34+
validErrs = append(validErrs, codersdk.ValidationError{
35+
Field: groupsField,
36+
Detail: err.Error(),
37+
})
38+
}
39+
// Validate that the IDs are UUIDs
40+
id, err := uuid.Parse(idStr)
41+
if err != nil {
42+
validErrs = append(validErrs, codersdk.ValidationError{
43+
Field: groupsField,
44+
Detail: idStr + "is not a valid UUID.",
45+
})
46+
continue
47+
}
48+
groupIDs = append(groupIDs, id)
49+
}
50+
51+
// Validate that the groups exist
52+
groupValidation, err := db.ValidateGroupIDs(ctx, groupIDs)
53+
if err != nil {
54+
validErrs = append(validErrs, codersdk.ValidationError{
55+
Field: groupsField,
56+
Detail: fmt.Sprintf("failed to validate group IDs: %v", err.Error()),
57+
})
58+
}
59+
if !groupValidation.Ok {
60+
for _, id := range groupValidation.InvalidGroupIds {
61+
validErrs = append(validErrs, codersdk.ValidationError{
62+
Field: groupsField,
63+
Detail: fmt.Sprintf("group with ID %v does not exist", id),
64+
})
65+
}
66+
}
67+
68+
userPerms, usersField := v.Users()
69+
userIDs := make([]uuid.UUID, 0, len(userPerms))
70+
for idStr, role := range userPerms {
71+
// Validate the provided role names
72+
if err := v.ValidateRole(role); err != nil {
73+
validErrs = append(validErrs, codersdk.ValidationError{
74+
Field: usersField,
75+
Detail: err.Error(),
76+
})
77+
}
78+
// Validate that the IDs are UUIDs
79+
id, err := uuid.Parse(idStr)
80+
if err != nil {
81+
validErrs = append(validErrs, codersdk.ValidationError{
82+
Field: usersField,
83+
Detail: idStr + "is not a valid UUID.",
84+
})
85+
continue
86+
}
87+
userIDs = append(userIDs, id)
88+
}
89+
90+
// Validate that the groups exist
91+
userValidation, err := db.ValidateUserIDs(ctx, userIDs)
92+
if err != nil {
93+
validErrs = append(validErrs, codersdk.ValidationError{
94+
Field: usersField,
95+
Detail: fmt.Sprintf("failed to validate user IDs: %v", err.Error()),
96+
})
97+
}
98+
if !userValidation.Ok {
99+
for _, id := range userValidation.InvalidUserIds {
100+
validErrs = append(validErrs, codersdk.ValidationError{
101+
Field: usersField,
102+
Detail: fmt.Sprintf("user with ID %v does not exist", id),
103+
})
104+
}
105+
}
106+
107+
return validErrs
108+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy