Compare commits

...

1 Commits

Author SHA1 Message Date
Ethan Dickson 9030e08581 fix(rbac): grant template admin read access to dormant workspaces
Template admins could list dormant workspaces (GetWorkspaces prepares
its SQL filter against the 'workspace' type) but could not read them
individually (GetWorkspaceByID checks the object's RBACObject() which
returns 'workspace_dormant' when DormantAt is set). This inconsistency
meant a template admin would see a dormant workspace in a list but get
a 403 trying to view it.

Add ActionRead on ResourceWorkspaceDormant to both the site-level
template-admin and org-level organization-template-admin roles. This
is the minimal grant needed to make the two paths consistent without
granting any lifecycle permissions (create, update, delete, stop, etc.)
on dormant workspaces.

Split the WorkspaceDormant RBAC test case into WorkspaceDormantRead
(ActionRead only) and WorkspaceDormant (remaining write/lifecycle
actions) so the new permission can be asserted independently.
2026-03-25 06:20:55 +00:00
2 changed files with 12 additions and 1 deletions
+2
View File
@@ -371,6 +371,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
// CRUD all files, even those they did not upload.
ResourceFile.Type: {policy.ActionCreate, policy.ActionRead},
ResourceWorkspace.Type: {policy.ActionRead},
ResourceWorkspaceDormant.Type: {policy.ActionRead},
ResourcePrebuiltWorkspace.Type: {policy.ActionUpdate, policy.ActionDelete},
// CRUD to provisioner daemons for now.
ResourceProvisionerDaemon.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
@@ -529,6 +530,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
ResourceTemplate.Type: ResourceTemplate.AvailableActions(),
ResourceFile.Type: {policy.ActionCreate, policy.ActionRead},
ResourceWorkspace.Type: {policy.ActionRead},
ResourceWorkspaceDormant.Type: {policy.ActionRead},
ResourcePrebuiltWorkspace.Type: {policy.ActionUpdate, policy.ActionDelete},
// Assigning template perms requires this permission.
ResourceOrganization.Type: {policy.ActionRead},
+10 -1
View File
@@ -580,9 +580,18 @@ func TestRolePermissions(t *testing.T) {
false: {setOtherOrg, memberMe},
},
},
{
Name: "WorkspaceDormantRead",
Actions: []policy.Action{policy.ActionRead},
Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
AuthorizeMap: map[bool][]hasAuthSubjects{
true: {orgAdmin, owner, templateAdmin, orgTemplateAdmin},
false: {setOtherOrg, userAdmin, memberMe, orgUserAdmin, orgAuditor},
},
},
{
Name: "WorkspaceDormant",
Actions: append(crud, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent),
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent},
Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
AuthorizeMap: map[bool][]hasAuthSubjects{
true: {orgAdmin, owner},