Skip to content

Commit 82b1faf

Browse files
committed
add groups acl
1 parent afe328b commit 82b1faf

File tree

11 files changed

+169
-34
lines changed

11 files changed

+169
-34
lines changed

coderd/database/databasefake/databasefake.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,7 +1233,7 @@ func (q *fakeQuerier) GetTemplates(_ context.Context) ([]database.Template, erro
12331233
return templates, nil
12341234
}
12351235

1236-
func (q *fakeQuerier) UpdateTemplateUserACLByID(_ context.Context, id uuid.UUID, acl database.UserACL) error {
1236+
func (q *fakeQuerier) UpdateTemplateUserACLByID(_ context.Context, id uuid.UUID, acl database.ACL) error {
12371237
q.mutex.RLock()
12381238
defer q.mutex.RUnlock()
12391239

@@ -1247,6 +1247,20 @@ func (q *fakeQuerier) UpdateTemplateUserACLByID(_ context.Context, id uuid.UUID,
12471247
return sql.ErrNoRows
12481248
}
12491249

1250+
func (q *fakeQuerier) UpdateTemplateGroupACLByID(_ context.Context, id uuid.UUID, acl database.ACL) error {
1251+
q.mutex.RLock()
1252+
defer q.mutex.RUnlock()
1253+
1254+
for i, t := range q.templates {
1255+
if t.ID == id {
1256+
t = t.SetGroupACL(acl)
1257+
q.templates[i] = t
1258+
return nil
1259+
}
1260+
}
1261+
return sql.ErrNoRows
1262+
}
1263+
12501264
func (q *fakeQuerier) GetTemplateUserRoles(_ context.Context, id uuid.UUID) ([]database.TemplateUser, error) {
12511265
q.mutex.RLock()
12521266
defer q.mutex.RUnlock()
@@ -1286,6 +1300,45 @@ func (q *fakeQuerier) GetTemplateUserRoles(_ context.Context, id uuid.UUID) ([]d
12861300
return users, nil
12871301
}
12881302

1303+
func (q *fakeQuerier) GetTemplateGroupRoles(_ context.Context, id uuid.UUID) ([]database.TemplateGroup, error) {
1304+
q.mutex.RLock()
1305+
defer q.mutex.RUnlock()
1306+
1307+
var template database.Template
1308+
for _, t := range q.templates {
1309+
if t.ID == id {
1310+
template = t
1311+
break
1312+
}
1313+
}
1314+
1315+
if template.ID == uuid.Nil {
1316+
return nil, sql.ErrNoRows
1317+
}
1318+
1319+
acl := template.UserACL()
1320+
1321+
groups := make([]database.TemplateGroup, 0, len(acl))
1322+
for k, v := range acl {
1323+
user, err := q.GetGroupByID(context.Background(), uuid.MustParse(k))
1324+
if err != nil && xerrors.Is(err, sql.ErrNoRows) {
1325+
return nil, xerrors.Errorf("get user by ID: %w", err)
1326+
}
1327+
// We don't delete users from the map if they
1328+
// get deleted so just skip.
1329+
if xerrors.Is(err, sql.ErrNoRows) {
1330+
continue
1331+
}
1332+
1333+
groups = append(groups, database.TemplateGroup{
1334+
Group: user,
1335+
Role: v,
1336+
})
1337+
}
1338+
1339+
return groups, nil
1340+
}
1341+
12891342
func (q *fakeQuerier) GetOrganizationMemberByUserID(_ context.Context, arg database.GetOrganizationMemberByUserIDParams) (database.OrganizationMember, error) {
12901343
q.mutex.RLock()
12911344
defer q.mutex.RUnlock()
@@ -1766,7 +1819,8 @@ func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
17661819
MinAutostartInterval: arg.MinAutostartInterval,
17671820
CreatedBy: arg.CreatedBy,
17681821
}
1769-
template = template.SetUserACL(database.UserACL{})
1822+
template = template.SetUserACL(database.ACL{})
1823+
template = template.SetGroupACL(database.ACL{})
17701824
q.templates = append(q.templates, template)
17711825
return template, nil
17721826
}

coderd/database/modelmethods.go

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,10 @@ import (
77
"github.com/coder/coder/coderd/rbac"
88
)
99

10-
// UserACL is a map of user_ids to permissions.
11-
type UserACL map[string]TemplateRole
10+
// ACL is a map of user_ids to permissions.
11+
type ACL map[string]TemplateRole
1212

13-
// Group is a map of user_ids to permissions.
14-
type GroupACL map[string]TemplateRole
15-
16-
func (u UserACL) Actions() map[string][]rbac.Action {
13+
func (u ACL) Actions() map[string][]rbac.Action {
1714
aclRBAC := make(map[string][]rbac.Action, len(u))
1815
for k, v := range u {
1916
aclRBAC[k] = templateRoleToActions(v)
@@ -22,8 +19,8 @@ func (u UserACL) Actions() map[string][]rbac.Action {
2219
return aclRBAC
2320
}
2421

25-
func (t Template) UserACL() UserACL {
26-
var acl UserACL
22+
func (t Template) UserACL() ACL {
23+
var acl ACL
2724
if len(t.userACL) == 0 {
2825
return acl
2926
}
@@ -36,9 +33,31 @@ func (t Template) UserACL() UserACL {
3633
return acl
3734
}
3835

39-
func (t Template) GroupACL() Gr
36+
func (t Template) GroupACL() ACL {
37+
var acl ACL
38+
if len(t.groupACL) == 0 {
39+
return acl
40+
}
41+
42+
err := json.Unmarshal(t.groupACL, &acl)
43+
if err != nil {
44+
panic(fmt.Sprintf("failed to unmarshal template.userACL: %v", err.Error()))
45+
}
46+
47+
return acl
48+
}
49+
50+
func (t Template) SetGroupACL(acl ACL) Template {
51+
raw, err := json.Marshal(acl)
52+
if err != nil {
53+
panic(fmt.Sprintf("marshal user acl: %v", err))
54+
}
55+
56+
t.userACL = raw
57+
return t
58+
}
4059

41-
func (t Template) SetUserACL(acl UserACL) Template {
60+
func (t Template) SetUserACL(acl ACL) Template {
4261
raw, err := json.Marshal(acl)
4362
if err != nil {
4463
panic(fmt.Sprintf("marshal user acl: %v", err))
@@ -74,7 +93,9 @@ func (s APIKeyScope) ToRBAC() rbac.Scope {
7493

7594
func (t Template) RBACObject() rbac.Object {
7695
obj := rbac.ResourceTemplate
77-
return obj.InOrg(t.OrganizationID).WithACLUserList(t.UserACL().Actions())
96+
return obj.InOrg(t.OrganizationID).
97+
WithACLUserList(t.UserACL().Actions()).
98+
WithGroupACL(t.GroupACL().Actions())
7899
}
79100

80101
func (TemplateVersion) RBACObject(template Template) rbac.Object {

coderd/database/modelqueries.go

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ type customQuerier interface {
1616
}
1717

1818
type templateQuerier interface {
19-
UpdateTemplateUserACLByID(ctx context.Context, id uuid.UUID, acl UserACL) error
19+
UpdateTemplateUserACLByID(ctx context.Context, id uuid.UUID, acl ACL) error
20+
UpdateTemplateGroupACLByID(ctx context.Context, id uuid.UUID, acl ACL) error
21+
GetTemplateGroupRoles(ctx context.Context, id uuid.UUID) ([]TemplateGroup, error)
2022
GetTemplateUserRoles(ctx context.Context, id uuid.UUID) ([]TemplateUser, error)
2123
}
2224

@@ -25,7 +27,7 @@ type TemplateUser struct {
2527
Role TemplateRole `db:"role"`
2628
}
2729

28-
func (q *sqlQuerier) UpdateTemplateUserACLByID(ctx context.Context, id uuid.UUID, acl UserACL) error {
30+
func (q *sqlQuerier) UpdateTemplateUserACLByID(ctx context.Context, id uuid.UUID, acl ACL) error {
2931
raw, err := json.Marshal(acl)
3032
if err != nil {
3133
return xerrors.Errorf("marshal user acl: %w", err)
@@ -81,3 +83,65 @@ func (q *sqlQuerier) GetTemplateUserRoles(ctx context.Context, id uuid.UUID) ([]
8183

8284
return tus, nil
8385
}
86+
87+
type TemplateGroup struct {
88+
Group
89+
Role TemplateRole
90+
}
91+
92+
func (q *sqlQuerier) UpdateTemplateGroupACLByID(ctx context.Context, id uuid.UUID, acl ACL) error {
93+
raw, err := json.Marshal(acl)
94+
if err != nil {
95+
return xerrors.Errorf("marshal user acl: %w", err)
96+
}
97+
98+
const query = `
99+
UPDATE
100+
templates
101+
SET
102+
group_acl = $2
103+
WHERE
104+
id = $1`
105+
106+
_, err = q.db.ExecContext(ctx, query, id.String(), raw)
107+
if err != nil {
108+
return xerrors.Errorf("update user acl: %w", err)
109+
}
110+
111+
return nil
112+
}
113+
114+
func (q *sqlQuerier) GetTemplateGroupRoles(ctx context.Context, id uuid.UUID) ([]TemplateGroup, error) {
115+
const query = `
116+
SELECT
117+
perms.value as role, group.*
118+
FROM
119+
groups
120+
JOIN
121+
(
122+
SELECT
123+
*
124+
FROM
125+
jsonb_each_text(
126+
(
127+
SELECT
128+
templates.group
129+
FROM
130+
templates
131+
WHERE
132+
id = $1
133+
)
134+
)
135+
) AS perms
136+
ON
137+
groups.id::text = perms.key;
138+
`
139+
140+
var tgs []TemplateGroup
141+
err := q.db.SelectContext(ctx, &tgs, query, id.String())
142+
if err != nil {
143+
return nil, xerrors.Errorf("select context: %w", err)
144+
}
145+
146+
return tgs, nil
147+
}

coderd/database/models.go

Lines changed: 1 addition & 1 deletion
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: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/sqlc.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ rename:
4040
ids: IDs
4141
jwt: JWT
4242
user_acl: userACL
43-
group_acl: userACL
43+
group_acl: groupACL

coderd/rbac/authz_internal_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,29 +244,29 @@ func TestAuthorizeDomain(t *testing.T) {
244244

245245
testAuthorize(t, "GroupACLList", user, []authTestCase{
246246
{
247-
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithGroups(map[string][]Action{
247+
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithGroupACL(map[string][]Action{
248248
allUsersGroup: allActions(),
249249
}),
250250
actions: allActions(),
251251
allow: true,
252252
},
253253
{
254-
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithGroups(map[string][]Action{
254+
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithGroupACL(map[string][]Action{
255255
allUsersGroup: {WildcardSymbol},
256256
}),
257257
actions: allActions(),
258258
allow: true,
259259
},
260260
{
261-
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithGroups(map[string][]Action{
261+
resource: ResourceWorkspace.WithOwner(unuseID.String()).InOrg(unuseID).WithGroupACL(map[string][]Action{
262262
allUsersGroup: {ActionRead, ActionUpdate},
263263
}),
264264
actions: []Action{ActionCreate, ActionDelete},
265265
allow: false,
266266
},
267267
{
268268
// By default users cannot update templates
269-
resource: ResourceTemplate.InOrg(defOrg).WithGroups(map[string][]Action{
269+
resource: ResourceTemplate.InOrg(defOrg).WithGroupACL(map[string][]Action{
270270
allUsersGroup: {ActionUpdate},
271271
}),
272272
actions: []Action{ActionRead, ActionUpdate},

coderd/rbac/object.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ func (z Object) WithACLUserList(acl map[string][]Action) Object {
208208
}
209209
}
210210

211-
func (z Object) WithGroups(groups map[string][]Action) Object {
211+
func (z Object) WithGroupACL(groups map[string][]Action) Object {
212212
return Object{
213213
Owner: z.Owner,
214214
OrgID: z.OrgID,

coderd/templates.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
466466

467467
// Only users who are able to create templates (aka template admins)
468468
// are able to control user permissions.
469-
if (len(req.UserPerms) > 0 || req.IsPrivate != nil) &&
469+
if len(req.UserPerms) > 0 &&
470470
!api.Authorize(r, rbac.ActionCreate, template) {
471471
httpapi.ResourceNotFound(rw)
472472
return
@@ -872,7 +872,7 @@ func (api *API) convertTemplate(
872872
}
873873
}
874874

875-
func convertTemplateACL(acl database.UserACL) map[string]codersdk.TemplateRole {
875+
func convertTemplateACL(acl database.ACL) map[string]codersdk.TemplateRole {
876876
userACL := make(map[string]codersdk.TemplateRole, len(acl))
877877
for k, v := range acl {
878878
userACL[k] = convertDatabaseTemplateRole(v)

coderd/templates_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,6 @@ func TestPatchTemplateMeta(t *testing.T) {
406406
Icon: "/icons/new-icon.png",
407407
MaxTTLMillis: 12 * time.Hour.Milliseconds(),
408408
MinAutostartIntervalMillis: time.Minute.Milliseconds(),
409-
IsPrivate: boolPtr(true),
410409
}
411410
// It is unfortunate we need to sleep, but the test can fail if the
412411
// updatedAt is too close together.
@@ -423,7 +422,6 @@ func TestPatchTemplateMeta(t *testing.T) {
423422
assert.Equal(t, req.Icon, updated.Icon)
424423
assert.Equal(t, req.MaxTTLMillis, updated.MaxTTLMillis)
425424
assert.Equal(t, req.MinAutostartIntervalMillis, updated.MinAutostartIntervalMillis)
426-
assert.True(t, updated.IsPrivate)
427425

428426
// Extra paranoid: did it _really_ happen?
429427
updated, err = client.Template(ctx, template.ID)
@@ -434,7 +432,6 @@ func TestPatchTemplateMeta(t *testing.T) {
434432
assert.Equal(t, req.Icon, updated.Icon)
435433
assert.Equal(t, req.MaxTTLMillis, updated.MaxTTLMillis)
436434
assert.Equal(t, req.MinAutostartIntervalMillis, updated.MinAutostartIntervalMillis)
437-
assert.Equal(t, *req.IsPrivate, updated.IsPrivate)
438435

439436
require.Len(t, auditor.AuditLogs, 4)
440437
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[3].Action)

0 commit comments

Comments
 (0)