From 116a220ff67c7acd745ffd96765357d9f1eb42b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McKayla=20=E3=81=AF=E3=81=AA?= Date: Mon, 22 Jun 2026 12:27:16 -0600 Subject: [PATCH] fix(coderd): let admins change their own workspace sharing role (#26559) --- coderd/workspaces.go | 14 ++++++++--- coderd/workspaces_test.go | 53 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index c4ed1e99f7..92eaf464e7 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -2425,10 +2425,16 @@ func (api *API) patchWorkspaceACL(rw http.ResponseWriter, r *http.Request) { apiKey := httpmw.APIKey(r) if _, ok := req.UserRoles[apiKey.UserID.String()]; ok { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "You cannot change your own workspace sharing role.", - }) - return + // Block changing your own sharing role unless you can share any + // workspace in the organization. This keeps a user whose only access is + // this share from destructively demoting themselves, while letting org + // and deployment admins manage their own access. + if !api.Authorize(r, policy.ActionShare, rbac.ResourceWorkspace.InOrg(workspace.OrganizationID)) { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "You cannot change your own workspace sharing role.", + }) + return + } } validErrs := acl.Validate(ctx, api.Database, WorkspaceACLUpdateValidator(req)) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 19d833dbaf..5d8cc7c150 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -5669,6 +5669,59 @@ func TestUpdateWorkspaceACL(t *testing.T) { }) require.NoError(t, err) }) + + //nolint:tparallel,paralleltest // Modifies package global rbac.workspaceACLDisabled. + t.Run("OrgAdminCanChangeOwnRole", func(t *testing.T) { + // Save and restore the global to avoid affecting other tests. + prevWorkspaceACLDisabled := rbac.WorkspaceACLDisabled() + rbac.SetWorkspaceACLDisabled(false) + t.Cleanup(func() { rbac.SetWorkspaceACLDisabled(prevWorkspaceACLDisabled) }) + + dv := coderdtest.DeploymentValues(t) + + adminClient := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + DeploymentValues: dv, + }) + adminUser := coderdtest.CreateFirstUser(t, adminClient) + orgID := adminUser.OrganizationID + workspaceOwnerClient, _ := coderdtest.CreateAnotherUser(t, adminClient, orgID) + orgAdminClient, orgAdminUser := coderdtest.CreateAnotherUser(t, adminClient, orgID, rbac.ScopedRoleOrgAdmin(orgID)) + + tv := coderdtest.CreateTemplateVersion(t, adminClient, orgID, nil) + coderdtest.AwaitTemplateVersionJobCompleted(t, adminClient, tv.ID) + template := coderdtest.CreateTemplate(t, adminClient, orgID, tv.ID) + + ws := coderdtest.CreateWorkspace(t, workspaceOwnerClient, template.ID) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, workspaceOwnerClient, ws.LatestBuild.ID) + + ctx := testutil.Context(t, testutil.WaitMedium) + + // An org admin can share a workspace they do not own with themselves, + // because they hold workspace share permission across the organization + // independent of this workspace's ACL. + err := orgAdminClient.UpdateWorkspaceACL(ctx, ws.ID, codersdk.UpdateWorkspaceACL{ + UserRoles: map[string]codersdk.WorkspaceRole{ + orgAdminUser.ID.String(): codersdk.WorkspaceRoleAdmin, + }, + }) + require.NoError(t, err) + + // They can also change and then remove their own role. + err = orgAdminClient.UpdateWorkspaceACL(ctx, ws.ID, codersdk.UpdateWorkspaceACL{ + UserRoles: map[string]codersdk.WorkspaceRole{ + orgAdminUser.ID.String(): codersdk.WorkspaceRoleUse, + }, + }) + require.NoError(t, err) + + err = orgAdminClient.UpdateWorkspaceACL(ctx, ws.ID, codersdk.UpdateWorkspaceACL{ + UserRoles: map[string]codersdk.WorkspaceRole{ + orgAdminUser.ID.String(): codersdk.WorkspaceRoleDeleted, + }, + }) + require.NoError(t, err) + }) } func TestDeleteWorkspaceACL(t *testing.T) {