From 73a77edd75fe969afd269a1d6b8940af97fa904d Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 7 Aug 2025 03:34:26 +0000 Subject: [PATCH 1/2] fix: use system context for querying workspaces when deleting users --- coderd/users.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/coderd/users.go b/coderd/users.go index 7fbb8e7d04cdf..851c52d71188e 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -542,7 +542,10 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) { return } - workspaces, err := api.Database.GetWorkspaces(ctx, database.GetWorkspacesParams{ + // This query is ONLY done to get the workspace count, so we use a system + // context to return ALL workspaces. Not just workspaces the user can view. + // nolint:gocritic + workspaces, err := api.Database.GetWorkspaces(dbauthz.AsSystemRestricted(ctx), database.GetWorkspacesParams{ OwnerID: user.ID, }) if err != nil { From bfcc771705ad7cf99636b5898ec216955c4c5ad9 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Thu, 7 Aug 2025 03:43:24 +0000 Subject: [PATCH 2/2] test --- coderd/users_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/coderd/users_test.go b/coderd/users_test.go index 9d695f37c9906..5928fc6486f51 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -377,6 +377,43 @@ func TestDeleteUser(t *testing.T) { require.ErrorAs(t, err, &apiErr, "should be a coderd error") require.Equal(t, http.StatusForbidden, apiErr.StatusCode(), "should be forbidden") }) + t.Run("CountCheckIncludesAllWorkspaces", func(t *testing.T) { + t.Parallel() + client, _ := coderdtest.NewWithProvisionerCloser(t, nil) + firstUser := coderdtest.CreateFirstUser(t, client) + + // Create a target user who will own a workspace + targetUserClient, targetUser := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID) + + // Create a User Admin who should not have permission to see the target user's workspace + userAdminClient, userAdmin := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID) + + // Grant User Admin role to the userAdmin + userAdmin, err := client.UpdateUserRoles(context.Background(), userAdmin.ID.String(), codersdk.UpdateRoles{ + Roles: []string{rbac.RoleUserAdmin().String()}, + }) + require.NoError(t, err) + + // Create a template and workspace owned by the target user + version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, nil) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, firstUser.OrganizationID, version.ID) + _ = coderdtest.CreateWorkspace(t, targetUserClient, template.ID) + + workspaces, err := userAdminClient.Workspaces(context.Background(), codersdk.WorkspaceFilter{ + Owner: targetUser.Username, + }) + require.NoError(t, err) + require.Len(t, workspaces.Workspaces, 0) + + // Attempt to delete the target user - this should fail because the + // user has a workspace not visible to the deleting user. + err = userAdminClient.DeleteUser(context.Background(), targetUser.ID) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusExpectationFailed, apiErr.StatusCode()) + require.Contains(t, apiErr.Message, "has workspaces") + }) } func TestNotifyUserStatusChanged(t *testing.T) {