Skip to content

Commit 6309ab8

Browse files
committed
Add user-admin role
1 parent e9e4a42 commit 6309ab8

File tree

6 files changed

+92
-45
lines changed

6 files changed

+92
-45
lines changed

coderd/rbac/builtin.go

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import (
99
)
1010

1111
const (
12-
admin string = "admin"
13-
member string = "member"
14-
templateManager string = "template-manager"
15-
auditor string = "auditor"
12+
admin string = "admin"
13+
member string = "member"
14+
templateAdmin string = "template-admin"
15+
userAdmin string = "user-admin"
16+
auditor string = "auditor"
1617

1718
orgAdmin string = "organization-admin"
1819
orgMember string = "organization-member"
@@ -27,6 +28,14 @@ func RoleAdmin() string {
2728
return roleName(admin, "")
2829
}
2930

31+
func RoleTemplateAdmin() string {
32+
return roleName(templateAdmin, "")
33+
}
34+
35+
func RoleUserAdmin() string {
36+
return roleName(userAdmin, "")
37+
}
38+
3039
func RoleMember() string {
3140
return roleName(member, "")
3241
}
@@ -73,7 +82,8 @@ var (
7382
ResourceProvisionerDaemon: {ActionRead},
7483
}),
7584
User: permissions(map[Object][]Action{
76-
ResourceWildcard: {WildcardSymbol},
85+
ResourceWildcard: {WildcardSymbol},
86+
ResourceWorkspaceExecution: {WildcardSymbol},
7787
}),
7888
}
7989
},
@@ -94,14 +104,25 @@ var (
94104
}
95105
},
96106

97-
templateManager: func(_ string) Role {
107+
templateAdmin: func(_ string) Role {
98108
return Role{
99-
Name: templateManager,
100-
DisplayName: "Template Manager",
109+
Name: templateAdmin,
110+
DisplayName: "Template Admin",
101111
Site: permissions(map[Object][]Action{
102112
ResourceTemplate: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
103113
// CRUD all files, even those they did not upload.
104-
ResourceFile: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
114+
ResourceFile: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
115+
ResourceWorkspace: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
116+
}),
117+
}
118+
},
119+
120+
userAdmin: func(_ string) Role {
121+
return Role{
122+
Name: userAdmin,
123+
DisplayName: "User Admin",
124+
Site: permissions(map[Object][]Action{
125+
ResourceUser: {ActionCreate, ActionRead, ActionUpdate, ActionDelete},
105126
}),
106127
}
107128
},
@@ -166,12 +187,13 @@ var (
166187
// map[actor_role][assign_role]<can_assign>
167188
assignRoles = map[string]map[string]bool{
168189
admin: {
169-
admin: true,
170-
auditor: true,
171-
member: true,
172-
orgAdmin: true,
173-
orgMember: true,
174-
templateManager: true,
190+
admin: true,
191+
auditor: true,
192+
member: true,
193+
orgAdmin: true,
194+
orgMember: true,
195+
templateAdmin: true,
196+
userAdmin: true,
175197
},
176198
orgAdmin: {
177199
orgAdmin: true,

coderd/rbac/builtin_internal_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ func TestRoleByName(t *testing.T) {
1818
}{
1919
{Role: builtInRoles[admin]("")},
2020
{Role: builtInRoles[member]("")},
21-
{Role: builtInRoles[templateManager]("")},
21+
{Role: builtInRoles[templateAdmin]("")},
22+
{Role: builtInRoles[userAdmin]("")},
2223
{Role: builtInRoles[auditor]("")},
2324

2425
{Role: builtInRoles[orgAdmin](uuid.New().String())},

coderd/rbac/builtin_test.go

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ func TestRolePermissions(t *testing.T) {
111111
// currentUser is anything that references "me", "mine", or "my".
112112
currentUser := uuid.New()
113113
adminID := uuid.New()
114+
templateAdminID := uuid.New()
114115
orgID := uuid.New()
115116
otherOrg := uuid.New()
116117

@@ -124,9 +125,12 @@ func TestRolePermissions(t *testing.T) {
124125
otherOrgMember := authSubject{Name: "org_member_other", UserID: uuid.NewString(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg)}}
125126
otherOrgAdmin := authSubject{Name: "org_admin_other", UserID: uuid.NewString(), Roles: []string{rbac.RoleMember(), rbac.RoleOrgMember(otherOrg), rbac.RoleOrgAdmin(otherOrg)}}
126127

128+
templateAdmin := authSubject{Name: "template-admin", UserID: templateAdminID.String(), Roles: []string{rbac.RoleMember(), rbac.RoleTemplateAdmin()}}
129+
userAdmin := authSubject{Name: "template-admin", UserID: templateAdminID.String(), Roles: []string{rbac.RoleMember(), rbac.RoleUserAdmin()}}
130+
127131
// requiredSubjects are required to be asserted in each test case. This is
128132
// to make sure one is not forgotten.
129-
requiredSubjects := []authSubject{memberMe, admin, orgMemberMe, orgAdmin, otherOrgAdmin, otherOrgMember}
133+
requiredSubjects := []authSubject{memberMe, admin, orgMemberMe, orgAdmin, otherOrgAdmin, otherOrgMember, templateAdmin, userAdmin}
130134

131135
testCases := []struct {
132136
// Name the test case to better locate the failing test case.
@@ -146,7 +150,7 @@ func TestRolePermissions(t *testing.T) {
146150
Actions: []rbac.Action{rbac.ActionRead},
147151
Resource: rbac.ResourceUser,
148152
AuthorizeMap: map[bool][]authSubject{
149-
true: {admin, memberMe, orgMemberMe, orgAdmin, otherOrgMember, otherOrgAdmin},
153+
true: {admin, memberMe, orgMemberMe, orgAdmin, otherOrgMember, otherOrgAdmin, templateAdmin, userAdmin},
150154
false: {},
151155
},
152156
},
@@ -155,8 +159,8 @@ func TestRolePermissions(t *testing.T) {
155159
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
156160
Resource: rbac.ResourceUser,
157161
AuthorizeMap: map[bool][]authSubject{
158-
true: {admin},
159-
false: {memberMe, orgMemberMe, orgAdmin, otherOrgMember, otherOrgAdmin},
162+
true: {admin, userAdmin},
163+
false: {memberMe, orgMemberMe, orgAdmin, otherOrgMember, otherOrgAdmin, templateAdmin},
160164
},
161165
},
162166
{
@@ -165,44 +169,54 @@ func TestRolePermissions(t *testing.T) {
165169
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
166170
Resource: rbac.ResourceWorkspace.InOrg(orgID).WithOwner(currentUser.String()),
167171
AuthorizeMap: map[bool][]authSubject{
168-
true: {admin, orgMemberMe, orgAdmin},
169-
false: {memberMe, otherOrgAdmin, otherOrgMember},
172+
true: {admin, orgMemberMe, orgAdmin, templateAdmin},
173+
false: {memberMe, otherOrgAdmin, otherOrgMember, userAdmin},
174+
},
175+
},
176+
{
177+
Name: "MyWorkspaceInOrgExecution",
178+
// When creating the WithID won't be set, but it does not change the result.
179+
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
180+
Resource: rbac.ResourceWorkspaceExecution.InOrg(orgID).WithOwner(currentUser.String()),
181+
AuthorizeMap: map[bool][]authSubject{
182+
true: {admin, orgAdmin, orgMemberMe},
183+
false: {memberMe, otherOrgAdmin, otherOrgMember, templateAdmin, userAdmin},
170184
},
171185
},
172186
{
173187
Name: "Templates",
174188
Actions: []rbac.Action{rbac.ActionCreate, rbac.ActionUpdate, rbac.ActionDelete},
175189
Resource: rbac.ResourceTemplate.InOrg(orgID),
176190
AuthorizeMap: map[bool][]authSubject{
177-
true: {admin, orgAdmin},
178-
false: {memberMe, orgMemberMe, otherOrgAdmin, otherOrgMember},
191+
true: {admin, orgAdmin, templateAdmin},
192+
false: {memberMe, orgMemberMe, otherOrgAdmin, otherOrgMember, userAdmin},
179193
},
180194
},
181195
{
182196
Name: "ReadTemplates",
183197
Actions: []rbac.Action{rbac.ActionRead},
184198
Resource: rbac.ResourceTemplate.InOrg(orgID),
185199
AuthorizeMap: map[bool][]authSubject{
186-
true: {admin, orgMemberMe, orgAdmin},
187-
false: {memberMe, otherOrgAdmin, otherOrgMember},
200+
true: {admin, orgMemberMe, orgAdmin, templateAdmin},
201+
false: {memberMe, otherOrgAdmin, otherOrgMember, userAdmin},
188202
},
189203
},
190204
{
191205
Name: "Files",
192206
Actions: []rbac.Action{rbac.ActionCreate},
193207
Resource: rbac.ResourceFile,
194208
AuthorizeMap: map[bool][]authSubject{
195-
true: {admin},
196-
false: {orgMemberMe, orgAdmin, memberMe, otherOrgAdmin, otherOrgMember},
209+
true: {admin, templateAdmin},
210+
false: {orgMemberMe, orgAdmin, memberMe, otherOrgAdmin, otherOrgMember, userAdmin},
197211
},
198212
},
199213
{
200214
Name: "MyFile",
201215
Actions: []rbac.Action{rbac.ActionRead, rbac.ActionUpdate, rbac.ActionDelete},
202216
Resource: rbac.ResourceFile.WithOwner(currentUser.String()),
203217
AuthorizeMap: map[bool][]authSubject{
204-
true: {admin, memberMe, orgMemberMe},
205-
false: {orgAdmin, otherOrgAdmin, otherOrgMember},
218+
true: {admin, memberMe, orgMemberMe, templateAdmin},
219+
false: {orgAdmin, otherOrgAdmin, otherOrgMember, userAdmin},
206220
},
207221
},
208222
{
@@ -211,7 +225,7 @@ func TestRolePermissions(t *testing.T) {
211225
Resource: rbac.ResourceOrganization,
212226
AuthorizeMap: map[bool][]authSubject{
213227
true: {admin},
214-
false: {orgAdmin, otherOrgAdmin, otherOrgMember, memberMe, orgMemberMe},
228+
false: {orgAdmin, otherOrgAdmin, otherOrgMember, memberMe, orgMemberMe, templateAdmin, userAdmin},
215229
},
216230
},
217231
{
@@ -220,7 +234,7 @@ func TestRolePermissions(t *testing.T) {
220234
Resource: rbac.ResourceOrganization.InOrg(orgID),
221235
AuthorizeMap: map[bool][]authSubject{
222236
true: {admin, orgAdmin},
223-
false: {otherOrgAdmin, otherOrgMember, memberMe, orgMemberMe},
237+
false: {otherOrgAdmin, otherOrgMember, memberMe, orgMemberMe, templateAdmin, userAdmin},
224238
},
225239
},
226240
{
@@ -229,7 +243,7 @@ func TestRolePermissions(t *testing.T) {
229243
Resource: rbac.ResourceOrganization.InOrg(orgID),
230244
AuthorizeMap: map[bool][]authSubject{
231245
true: {admin, orgAdmin, orgMemberMe},
232-
false: {otherOrgAdmin, otherOrgMember, memberMe},
246+
false: {otherOrgAdmin, otherOrgMember, memberMe, templateAdmin, userAdmin},
233247
},
234248
},
235249
{
@@ -238,15 +252,15 @@ func TestRolePermissions(t *testing.T) {
238252
Resource: rbac.ResourceRoleAssignment,
239253
AuthorizeMap: map[bool][]authSubject{
240254
true: {admin},
241-
false: {orgAdmin, orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe},
255+
false: {orgAdmin, orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe, templateAdmin, userAdmin},
242256
},
243257
},
244258
{
245259
Name: "ReadRoleAssignment",
246260
Actions: []rbac.Action{rbac.ActionRead},
247261
Resource: rbac.ResourceRoleAssignment,
248262
AuthorizeMap: map[bool][]authSubject{
249-
true: {admin, orgAdmin, orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe},
263+
true: {admin, orgAdmin, orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe, templateAdmin, userAdmin},
250264
false: {},
251265
},
252266
},
@@ -256,7 +270,7 @@ func TestRolePermissions(t *testing.T) {
256270
Resource: rbac.ResourceOrgRoleAssignment.InOrg(orgID),
257271
AuthorizeMap: map[bool][]authSubject{
258272
true: {admin, orgAdmin},
259-
false: {orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe},
273+
false: {orgMemberMe, otherOrgAdmin, otherOrgMember, memberMe, templateAdmin, userAdmin},
260274
},
261275
},
262276
{
@@ -265,7 +279,7 @@ func TestRolePermissions(t *testing.T) {
265279
Resource: rbac.ResourceOrgRoleAssignment.InOrg(orgID),
266280
AuthorizeMap: map[bool][]authSubject{
267281
true: {admin, orgAdmin, orgMemberMe},
268-
false: {otherOrgAdmin, otherOrgMember, memberMe},
282+
false: {otherOrgAdmin, otherOrgMember, memberMe, templateAdmin, userAdmin},
269283
},
270284
},
271285
{
@@ -274,7 +288,7 @@ func TestRolePermissions(t *testing.T) {
274288
Resource: rbac.ResourceAPIKey.WithOwner(currentUser.String()),
275289
AuthorizeMap: map[bool][]authSubject{
276290
true: {admin, orgMemberMe, memberMe},
277-
false: {orgAdmin, otherOrgAdmin, otherOrgMember},
291+
false: {orgAdmin, otherOrgAdmin, otherOrgMember, templateAdmin, userAdmin},
278292
},
279293
},
280294
{
@@ -283,7 +297,7 @@ func TestRolePermissions(t *testing.T) {
283297
Resource: rbac.ResourceUserData.WithOwner(currentUser.String()),
284298
AuthorizeMap: map[bool][]authSubject{
285299
true: {admin, orgMemberMe, memberMe},
286-
false: {orgAdmin, otherOrgAdmin, otherOrgMember},
300+
false: {orgAdmin, otherOrgAdmin, otherOrgMember, templateAdmin, userAdmin},
287301
},
288302
},
289303
{
@@ -292,7 +306,7 @@ func TestRolePermissions(t *testing.T) {
292306
Resource: rbac.ResourceOrganizationMember.InOrg(orgID),
293307
AuthorizeMap: map[bool][]authSubject{
294308
true: {admin, orgAdmin},
295-
false: {orgMemberMe, memberMe, otherOrgAdmin, otherOrgMember},
309+
false: {orgMemberMe, memberMe, otherOrgAdmin, otherOrgMember, templateAdmin, userAdmin},
296310
},
297311
},
298312
{
@@ -301,7 +315,7 @@ func TestRolePermissions(t *testing.T) {
301315
Resource: rbac.ResourceOrganizationMember.InOrg(orgID),
302316
AuthorizeMap: map[bool][]authSubject{
303317
true: {admin, orgAdmin, orgMemberMe},
304-
false: {memberMe, otherOrgAdmin, otherOrgMember},
318+
false: {memberMe, otherOrgAdmin, otherOrgMember, templateAdmin, userAdmin},
305319
},
306320
},
307321
}
@@ -402,7 +416,8 @@ func TestListRoles(t *testing.T) {
402416
"admin",
403417
"member",
404418
"auditor",
405-
"template-manager",
419+
"template-admin",
420+
"user-admin",
406421
},
407422
siteRoleNames)
408423

coderd/rbac/object.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ var (
2222
Type: "workspace",
2323
}
2424

25+
// ResourceWorkspaceExecution CRUD. Org + User owner
26+
// create = workspace remote execution
27+
// read = ?
28+
// update = ?
29+
// delete = ?
30+
ResourceWorkspaceExecution = Object{
31+
Type: "workspace_execution",
32+
}
33+
2534
// ResourceAuditLog
2635
// read = access audit log
2736
ResourceAuditLog = Object{

coderd/roles.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ func (api *API) assignableSiteRoles(rw http.ResponseWriter, r *http.Request) {
3232

3333
// assignableSiteRoles returns all site wide roles that can be assigned.
3434
func (api *API) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) {
35-
// TODO: @emyrk in the future, allow granular subsets of roles to be returned based on the
36-
// role of the user.
3735
organization := httpmw.OrganizationParam(r)
3836
actorRoles := httpmw.AuthorizationUserRoles(r)
3937

coderd/workspaceapps.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request)
4343
})
4444
return
4545
}
46-
if !api.Authorize(r, rbac.ActionRead, workspace) {
46+
47+
if !api.Authorize(r, rbac.ActionCreate,
48+
rbac.ResourceWorkspaceExecution.InOrg(workspace.OrganizationID).WithOwner(workspace.OwnerID.String())) {
4749
httpapi.ResourceNotFound(rw)
4850
return
4951
}

0 commit comments

Comments
 (0)