feat: allow TemplateAdmin to delete prebuilds via auth layer (#18333)

## Description

This PR adds support for deleting prebuilt workspaces via the
authorization layer. It introduces special-case handling to ensure that
`prebuilt_workspace` permissions are evaluated when attempting to delete
a prebuilt workspace, falling back to the standard `workspace` resource
as needed.

Prebuilt workspaces are a subset of workspaces, identified by having
`owner_id` set to `PREBUILD_SYSTEM_USER`.
This means:
* A user with `prebuilt_workspace.delete` permission is allowed to
**delete only prebuilt workspaces**.
* A user with `workspace.delete` permission can **delete both normal and
prebuilt workspaces**.

⚠️ This implementation is scoped to **deletion operations only**. No
other operations are currently supported for the `prebuilt_workspace`
resource.

To delete a workspace, users must have the following permissions:
* `workspace.read`: to read the current workspace state
* `update`: to modify workspace metadata and related resources during
deletion (e.g., updating the `deleted` field in the database)
* `delete`: to perform the actual deletion of the workspace

## Changes

* Introduced `authorizeWorkspace()` helper to handle prebuilt workspace
authorization logic.
* Ensured both `prebuilt_workspace` and `workspace` permissions are
checked.
* Added comments to clarify the current behavior and limitations.
* Moved `SystemUserID` constant from the `prebuilds` package to the
`database` package `PrebuildsSystemUserID` to resolve an import cycle
(commit
https://github.com/coder/coder/pull/18333/commits/f24e4ab4b6f0a56726fd04be2d7302c9fdb52d53).
* Update middleware `ExtractOrganizationMember` to include system user
members.
This commit is contained in:
Susana Ferreira
2025-06-20 17:36:32 +01:00
committed by GitHub
parent d61353f468
commit 72f7d70bab
29 changed files with 493 additions and 63 deletions
+5
View File
@@ -206,6 +206,7 @@ Status Code **200**
| `resource_type` | `oauth2_app_secret` |
| `resource_type` | `organization` |
| `resource_type` | `organization_member` |
| `resource_type` | `prebuilt_workspace` |
| `resource_type` | `provisioner_daemon` |
| `resource_type` | `provisioner_jobs` |
| `resource_type` | `replicas` |
@@ -375,6 +376,7 @@ Status Code **200**
| `resource_type` | `oauth2_app_secret` |
| `resource_type` | `organization` |
| `resource_type` | `organization_member` |
| `resource_type` | `prebuilt_workspace` |
| `resource_type` | `provisioner_daemon` |
| `resource_type` | `provisioner_jobs` |
| `resource_type` | `replicas` |
@@ -544,6 +546,7 @@ Status Code **200**
| `resource_type` | `oauth2_app_secret` |
| `resource_type` | `organization` |
| `resource_type` | `organization_member` |
| `resource_type` | `prebuilt_workspace` |
| `resource_type` | `provisioner_daemon` |
| `resource_type` | `provisioner_jobs` |
| `resource_type` | `replicas` |
@@ -682,6 +685,7 @@ Status Code **200**
| `resource_type` | `oauth2_app_secret` |
| `resource_type` | `organization` |
| `resource_type` | `organization_member` |
| `resource_type` | `prebuilt_workspace` |
| `resource_type` | `provisioner_daemon` |
| `resource_type` | `provisioner_jobs` |
| `resource_type` | `replicas` |
@@ -1042,6 +1046,7 @@ Status Code **200**
| `resource_type` | `oauth2_app_secret` |
| `resource_type` | `organization` |
| `resource_type` | `organization_member` |
| `resource_type` | `prebuilt_workspace` |
| `resource_type` | `provisioner_daemon` |
| `resource_type` | `provisioner_jobs` |
| `resource_type` | `replicas` |
+1
View File
@@ -6329,6 +6329,7 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
| `oauth2_app_secret` |
| `organization` |
| `organization_member` |
| `prebuilt_workspace` |
| `provisioner_daemon` |
| `provisioner_jobs` |
| `replicas` |