feat: implement acl for workspaces (#19094)

This commit is contained in:
ケイラ
2025-07-30 17:02:51 -06:00
committed by GitHub
parent d736af1fa3
commit eeb0bbefb9
17 changed files with 346 additions and 141 deletions
+2
View File
@@ -168,6 +168,8 @@ func TestGenerator(t *testing.T) {
DeletingAt: w.DeletingAt,
AutomaticUpdates: w.AutomaticUpdates,
Favorite: w.Favorite,
GroupACL: database.WorkspaceACL{},
UserACL: database.WorkspaceACL{},
}
require.Equal(t, exp, table)
})
+5 -1
View File
@@ -2262,7 +2262,9 @@ CREATE TABLE workspaces (
deleting_at timestamp with time zone,
automatic_updates automatic_updates DEFAULT 'never'::automatic_updates NOT NULL,
favorite boolean DEFAULT false NOT NULL,
next_start_at timestamp with time zone
next_start_at timestamp with time zone,
group_acl jsonb DEFAULT '{}'::jsonb NOT NULL,
user_acl jsonb DEFAULT '{}'::jsonb NOT NULL
);
COMMENT ON COLUMN workspaces.favorite IS 'Favorite is true if the workspace owner has favorited the workspace.';
@@ -2441,6 +2443,8 @@ CREATE VIEW workspaces_expanded AS
workspaces.automatic_updates,
workspaces.favorite,
workspaces.next_start_at,
workspaces.group_acl,
workspaces.user_acl,
visible_users.avatar_url AS owner_avatar_url,
visible_users.username AS owner_username,
visible_users.name AS owner_name,
@@ -0,0 +1,40 @@
DROP VIEW workspaces_expanded;
ALTER TABLE workspaces
DROP COLUMN group_acl,
DROP COLUMN user_acl;
CREATE VIEW workspaces_expanded AS
SELECT workspaces.id,
workspaces.created_at,
workspaces.updated_at,
workspaces.owner_id,
workspaces.organization_id,
workspaces.template_id,
workspaces.deleted,
workspaces.name,
workspaces.autostart_schedule,
workspaces.ttl,
workspaces.last_used_at,
workspaces.dormant_at,
workspaces.deleting_at,
workspaces.automatic_updates,
workspaces.favorite,
workspaces.next_start_at,
visible_users.avatar_url AS owner_avatar_url,
visible_users.username AS owner_username,
visible_users.name AS owner_name,
organizations.name AS organization_name,
organizations.display_name AS organization_display_name,
organizations.icon AS organization_icon,
organizations.description AS organization_description,
templates.name AS template_name,
templates.display_name AS template_display_name,
templates.icon AS template_icon,
templates.description AS template_description
FROM (((workspaces
JOIN visible_users ON ((workspaces.owner_id = visible_users.id)))
JOIN organizations ON ((workspaces.organization_id = organizations.id)))
JOIN templates ON ((workspaces.template_id = templates.id)));
COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.';
@@ -0,0 +1,43 @@
DROP VIEW workspaces_expanded;
ALTER TABLE workspaces
ADD COLUMN group_acl jsonb not null default '{}'::jsonb,
ADD COLUMN user_acl jsonb not null default '{}'::jsonb;
-- Recreate the view, now including the new columns
CREATE VIEW workspaces_expanded AS
SELECT workspaces.id,
workspaces.created_at,
workspaces.updated_at,
workspaces.owner_id,
workspaces.organization_id,
workspaces.template_id,
workspaces.deleted,
workspaces.name,
workspaces.autostart_schedule,
workspaces.ttl,
workspaces.last_used_at,
workspaces.dormant_at,
workspaces.deleting_at,
workspaces.automatic_updates,
workspaces.favorite,
workspaces.next_start_at,
workspaces.group_acl,
workspaces.user_acl,
visible_users.avatar_url AS owner_avatar_url,
visible_users.username AS owner_username,
visible_users.name AS owner_name,
organizations.name AS organization_name,
organizations.display_name AS organization_display_name,
organizations.icon AS organization_icon,
organizations.description AS organization_description,
templates.name AS template_name,
templates.display_name AS template_display_name,
templates.icon AS template_icon,
templates.description AS template_description
FROM (((workspaces
JOIN visible_users ON ((workspaces.owner_id = visible_users.id)))
JOIN organizations ON ((workspaces.organization_id = organizations.id)))
JOIN templates ON ((workspaces.template_id = templates.id)));
COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.';
+2
View File
@@ -242,6 +242,8 @@ func (w Workspace) WorkspaceTable() WorkspaceTable {
AutomaticUpdates: w.AutomaticUpdates,
Favorite: w.Favorite,
NextStartAt: w.NextStartAt,
GroupACL: w.GroupACL,
UserACL: w.UserACL,
}
}
+2
View File
@@ -298,6 +298,8 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
&i.OwnerAvatarUrl,
&i.OwnerUsername,
&i.OwnerName,
+4
View File
@@ -3851,6 +3851,8 @@ type Workspace struct {
AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"`
Favorite bool `db:"favorite" json:"favorite"`
NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"`
GroupACL WorkspaceACL `db:"group_acl" json:"group_acl"`
UserACL WorkspaceACL `db:"user_acl" json:"user_acl"`
OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"`
OwnerUsername string `db:"owner_username" json:"owner_username"`
OwnerName string `db:"owner_name" json:"owner_name"`
@@ -4272,4 +4274,6 @@ type WorkspaceTable struct {
// Favorite is true if the workspace owner has favorited the workspace.
Favorite bool `db:"favorite" json:"favorite"`
NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"`
GroupACL WorkspaceACL `db:"group_acl" json:"group_acl"`
UserACL WorkspaceACL `db:"user_acl" json:"user_acl"`
}
+43 -15
View File
@@ -15382,7 +15382,7 @@ func (q *sqlQuerier) DeleteWorkspaceSubAgentByID(ctx context.Context, id uuid.UU
const getWorkspaceAgentAndLatestBuildByAuthToken = `-- name: GetWorkspaceAgentAndLatestBuildByAuthToken :one
SELECT
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at,
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl,
workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.expanded_directory, workspace_agents.logs_length, workspace_agents.logs_overflowed, workspace_agents.started_at, workspace_agents.ready_at, workspace_agents.subsystems, workspace_agents.display_apps, workspace_agents.api_version, workspace_agents.display_order, workspace_agents.parent_id, workspace_agents.api_key_scope, workspace_agents.deleted,
workspace_build_with_user.id, workspace_build_with_user.created_at, workspace_build_with_user.updated_at, workspace_build_with_user.workspace_id, workspace_build_with_user.template_version_id, workspace_build_with_user.build_number, workspace_build_with_user.transition, workspace_build_with_user.initiator_id, workspace_build_with_user.provisioner_state, workspace_build_with_user.job_id, workspace_build_with_user.deadline, workspace_build_with_user.reason, workspace_build_with_user.daily_cost, workspace_build_with_user.max_deadline, workspace_build_with_user.template_version_preset_id, workspace_build_with_user.has_ai_task, workspace_build_with_user.ai_task_sidebar_app_id, workspace_build_with_user.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username, workspace_build_with_user.initiator_by_name
FROM
@@ -15444,6 +15444,8 @@ func (q *sqlQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Cont
&i.WorkspaceTable.AutomaticUpdates,
&i.WorkspaceTable.Favorite,
&i.WorkspaceTable.NextStartAt,
&i.WorkspaceTable.GroupACL,
&i.WorkspaceTable.UserACL,
&i.WorkspaceAgent.ID,
&i.WorkspaceAgent.CreatedAt,
&i.WorkspaceAgent.UpdatedAt,
@@ -19534,7 +19536,7 @@ func (q *sqlQuerier) GetDeploymentWorkspaceStats(ctx context.Context) (GetDeploy
const getWorkspaceByAgentID = `-- name: GetWorkspaceByAgentID :one
SELECT
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description
FROM
workspaces_expanded as workspaces
WHERE
@@ -19582,6 +19584,8 @@ func (q *sqlQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUI
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
&i.OwnerAvatarUrl,
&i.OwnerUsername,
&i.OwnerName,
@@ -19599,7 +19603,7 @@ func (q *sqlQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUI
const getWorkspaceByID = `-- name: GetWorkspaceByID :one
SELECT
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description
FROM
workspaces_expanded
WHERE
@@ -19628,6 +19632,8 @@ func (q *sqlQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Worksp
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
&i.OwnerAvatarUrl,
&i.OwnerUsername,
&i.OwnerName,
@@ -19645,7 +19651,7 @@ func (q *sqlQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Worksp
const getWorkspaceByOwnerIDAndName = `-- name: GetWorkspaceByOwnerIDAndName :one
SELECT
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description
FROM
workspaces_expanded as workspaces
WHERE
@@ -19681,6 +19687,8 @@ func (q *sqlQuerier) GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWo
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
&i.OwnerAvatarUrl,
&i.OwnerUsername,
&i.OwnerName,
@@ -19698,7 +19706,7 @@ func (q *sqlQuerier) GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWo
const getWorkspaceByResourceID = `-- name: GetWorkspaceByResourceID :one
SELECT
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description
FROM
workspaces_expanded as workspaces
WHERE
@@ -19741,6 +19749,8 @@ func (q *sqlQuerier) GetWorkspaceByResourceID(ctx context.Context, resourceID uu
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
&i.OwnerAvatarUrl,
&i.OwnerUsername,
&i.OwnerName,
@@ -19758,7 +19768,7 @@ func (q *sqlQuerier) GetWorkspaceByResourceID(ctx context.Context, resourceID uu
const getWorkspaceByWorkspaceAppID = `-- name: GetWorkspaceByWorkspaceAppID :one
SELECT
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description
FROM
workspaces_expanded as workspaces
WHERE
@@ -19813,6 +19823,8 @@ func (q *sqlQuerier) GetWorkspaceByWorkspaceAppID(ctx context.Context, workspace
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
&i.OwnerAvatarUrl,
&i.OwnerUsername,
&i.OwnerName,
@@ -19873,7 +19885,7 @@ SELECT
),
filtered_workspaces AS (
SELECT
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.owner_avatar_url, workspaces.owner_username, workspaces.owner_name, workspaces.organization_name, workspaces.organization_display_name, workspaces.organization_icon, workspaces.organization_description, workspaces.template_name, workspaces.template_display_name, workspaces.template_icon, workspaces.template_description,
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, workspaces.owner_avatar_url, workspaces.owner_username, workspaces.owner_name, workspaces.organization_name, workspaces.organization_display_name, workspaces.organization_icon, workspaces.organization_description, workspaces.template_name, workspaces.template_display_name, workspaces.template_icon, workspaces.template_description,
latest_build.template_version_id,
latest_build.template_version_name,
latest_build.completed_at as latest_build_completed_at,
@@ -20138,7 +20150,7 @@ WHERE
-- @authorize_filter
), filtered_workspaces_order AS (
SELECT
fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_ai_task
fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_ai_task
FROM
filtered_workspaces fw
ORDER BY
@@ -20159,7 +20171,7 @@ WHERE
$21
), filtered_workspaces_order_with_summary AS (
SELECT
fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_ai_task
fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_ai_task
FROM
filtered_workspaces_order fwo
-- Return a technical summary row with total count of workspaces.
@@ -20182,6 +20194,8 @@ WHERE
'never'::automatic_updates, -- automatic_updates
false, -- favorite
'0001-01-01 00:00:00+00'::timestamptz, -- next_start_at
'{}'::jsonb, -- group_acl
'{}'::jsonb, -- user_acl
'', -- owner_avatar_url
'', -- owner_username
'', -- owner_name
@@ -20211,7 +20225,7 @@ WHERE
filtered_workspaces
)
SELECT
fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_ai_task,
fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_ai_task,
tc.count
FROM
filtered_workspaces_order_with_summary fwos
@@ -20262,6 +20276,8 @@ type GetWorkspacesRow struct {
AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"`
Favorite bool `db:"favorite" json:"favorite"`
NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"`
GroupACL json.RawMessage `db:"group_acl" json:"group_acl"`
UserACL json.RawMessage `db:"user_acl" json:"user_acl"`
OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"`
OwnerUsername string `db:"owner_username" json:"owner_username"`
OwnerName string `db:"owner_name" json:"owner_name"`
@@ -20337,6 +20353,8 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams)
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
&i.OwnerAvatarUrl,
&i.OwnerUsername,
&i.OwnerName,
@@ -20451,7 +20469,7 @@ func (q *sqlQuerier) GetWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerI
}
const getWorkspacesByTemplateID = `-- name: GetWorkspacesByTemplateID :many
SELECT id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at FROM workspaces WHERE template_id = $1 AND deleted = false
SELECT id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl FROM workspaces WHERE template_id = $1 AND deleted = false
`
func (q *sqlQuerier) GetWorkspacesByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceTable, error) {
@@ -20480,6 +20498,8 @@ func (q *sqlQuerier) GetWorkspacesByTemplateID(ctx context.Context, templateID u
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
); err != nil {
return nil, err
}
@@ -20667,7 +20687,7 @@ INSERT INTO
next_start_at
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl
`
type InsertWorkspaceParams struct {
@@ -20718,6 +20738,8 @@ func (q *sqlQuerier) InsertWorkspace(ctx context.Context, arg InsertWorkspacePar
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
)
return i, err
}
@@ -20757,7 +20779,7 @@ SET
WHERE
id = $1
AND deleted = false
RETURNING id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at
RETURNING id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl
`
type UpdateWorkspaceParams struct {
@@ -20785,6 +20807,8 @@ func (q *sqlQuerier) UpdateWorkspace(ctx context.Context, arg UpdateWorkspacePar
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
)
return i, err
}
@@ -20873,7 +20897,7 @@ WHERE
workspaces.id = $1
AND templates.id = workspaces.template_id
RETURNING
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl
`
type UpdateWorkspaceDormantDeletingAtParams struct {
@@ -20901,6 +20925,8 @@ func (q *sqlQuerier) UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg U
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
)
return i, err
}
@@ -20975,7 +21001,7 @@ WHERE
template_id = $3
AND
dormant_at IS NOT NULL
RETURNING id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at
RETURNING id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl
`
type UpdateWorkspacesDormantDeletingAtByTemplateIDParams struct {
@@ -21010,6 +21036,8 @@ func (q *sqlQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.C
&i.AutomaticUpdates,
&i.Favorite,
&i.NextStartAt,
&i.GroupACL,
&i.UserACL,
); err != nil {
return nil, err
}
+2
View File
@@ -418,6 +418,8 @@ WHERE
'never'::automatic_updates, -- automatic_updates
false, -- favorite
'0001-01-01 00:00:00+00'::timestamptz, -- next_start_at
'{}'::jsonb, -- group_acl
'{}'::jsonb, -- user_acl
'', -- owner_avatar_url
'', -- owner_username
'', -- owner_name
+12
View File
@@ -73,6 +73,18 @@ sql:
- column: "template_usage_stats.app_usage_mins"
go_type:
type: "StringMapOfInt"
- column: "workspaces.user_acl"
go_type:
type: "WorkspaceACL"
- column: "workspaces.group_acl"
go_type:
type: "WorkspaceACL"
- column: "workspaces_expanded.user_acl"
go_type:
type: "WorkspaceACL"
- column: "workspaces_expanded.group_acl"
go_type:
type: "WorkspaceACL"
- column: "notification_templates.actions"
go_type:
type: "[]byte"
+22
View File
@@ -77,6 +77,28 @@ func (t TemplateACL) Value() (driver.Value, error) {
return json.Marshal(t)
}
type WorkspaceACL map[string]WorkspaceACLEntry
func (t *WorkspaceACL) Scan(src interface{}) error {
switch v := src.(type) {
case string:
return json.Unmarshal([]byte(v), &t)
case []byte, json.RawMessage:
//nolint
return json.Unmarshal(v.([]byte), &t)
}
return xerrors.Errorf("unexpected type %T", src)
}
func (t WorkspaceACL) Value() (driver.Value, error) {
return json.Marshal(t)
}
type WorkspaceACLEntry struct {
Permissions []policy.Action `json:"permissions"`
}
type ExternalAuthProvider struct {
ID string `json:"id"`
Optional bool `json:"optional,omitempty"`
-104
View File
@@ -1,104 +0,0 @@
package regosql
import (
"fmt"
"golang.org/x/xerrors"
"github.com/open-policy-agent/opa/ast"
"github.com/coder/coder/v2/coderd/rbac/regosql/sqltypes"
)
var (
_ sqltypes.VariableMatcher = ACLGroupVar{}
_ sqltypes.Node = ACLGroupVar{}
)
// ACLGroupVar is a variable matcher that handles group_acl and user_acl.
// The sql type is a jsonb object with the following structure:
//
// "group_acl": {
// "<group_name>": ["<actions>"]
// }
//
// This is a custom variable matcher as json objects have arbitrary complexity.
type ACLGroupVar struct {
StructSQL string
// input.object.group_acl -> ["input", "object", "group_acl"]
StructPath []string
// FieldReference handles referencing the subfields, which could be
// more variables. We pass one in as the global one might not be correctly
// scoped.
FieldReference sqltypes.VariableMatcher
// Instance fields
Source sqltypes.RegoSource
GroupNode sqltypes.Node
}
func ACLGroupMatcher(fieldReference sqltypes.VariableMatcher, structSQL string, structPath []string) ACLGroupVar {
return ACLGroupVar{StructSQL: structSQL, StructPath: structPath, FieldReference: fieldReference}
}
func (ACLGroupVar) UseAs() sqltypes.Node { return ACLGroupVar{} }
func (g ACLGroupVar) ConvertVariable(rego ast.Ref) (sqltypes.Node, bool) {
// "left" will be a map of group names to actions in rego.
// {
// "all_users": ["read"]
// }
left, err := sqltypes.RegoVarPath(g.StructPath, rego)
if err != nil {
return nil, false
}
aclGrp := ACLGroupVar{
StructSQL: g.StructSQL,
StructPath: g.StructPath,
FieldReference: g.FieldReference,
Source: sqltypes.RegoSource(rego.String()),
}
// We expect 1 more term. Either a ref or a string.
if len(left) != 1 {
return nil, false
}
// If the remaining is a variable, then we need to convert it.
// Assuming we support variable fields.
ref, ok := left[0].Value.(ast.Ref)
if ok && g.FieldReference != nil {
groupNode, ok := g.FieldReference.ConvertVariable(ref)
if ok {
aclGrp.GroupNode = groupNode
return aclGrp, true
}
}
// If it is a string, we assume it is a literal
groupName, ok := left[0].Value.(ast.String)
if ok {
aclGrp.GroupNode = sqltypes.String(string(groupName))
return aclGrp, true
}
// If we have not matched it yet, then it is something we do not recognize.
return nil, false
}
func (g ACLGroupVar) SQLString(cfg *sqltypes.SQLGenerator) string {
return fmt.Sprintf("%s->%s", g.StructSQL, g.GroupNode.SQLString(cfg))
}
func (g ACLGroupVar) ContainsSQL(cfg *sqltypes.SQLGenerator, other sqltypes.Node) (string, error) {
switch other.UseAs().(type) {
// Only supports containing other strings.
case sqltypes.AstString:
return fmt.Sprintf("%s ? %s", g.SQLString(cfg), other.SQLString(cfg)), nil
default:
return "", xerrors.Errorf("unsupported acl group contains %T", other)
}
}
+126
View File
@@ -0,0 +1,126 @@
package regosql
import (
"fmt"
"golang.org/x/xerrors"
"github.com/open-policy-agent/opa/ast"
"github.com/coder/coder/v2/coderd/rbac/regosql/sqltypes"
)
var (
_ sqltypes.VariableMatcher = ACLMappingVar{}
_ sqltypes.Node = ACLMappingVar{}
)
// ACLMappingVar is a variable matcher that handles group_acl and user_acl.
// The sql type is a jsonb object with the following structure:
//
// "group_acl": {
// "<group_name>": ["<actions>"]
// }
//
// This is a custom variable matcher as json objects have arbitrary complexity.
type ACLMappingVar struct {
// SelectSQL is used to `SELECT` the ACL mapping from the table for the
// given resource. ie. if the full query might look like `SELECT group_acl
// FROM things;` then you would want this to be `"group_acl"`.
SelectSQL string
// IndexMatcher handles variable references when indexing into the mapping.
// (ie. `input.object.acl_group_list[input.object.org_owner]`). We need one
// from the local context because the global one might not be correctly
// scoped.
IndexMatcher sqltypes.VariableMatcher
// Used if the action list isn't directly in the ACL entry. For example, in
// the `workspaces.group_acl` and `workspaces.user_acl` columns they're stored
// under a `"permissions"` key.
Subfield string
// StructPath represents the path of the value in rego
// ie. input.object.group_acl -> ["input", "object", "group_acl"]
StructPath []string
// Instance fields
Source sqltypes.RegoSource
GroupNode sqltypes.Node
}
func ACLMappingMatcher(indexMatcher sqltypes.VariableMatcher, selectSQL string, structPath []string) ACLMappingVar {
return ACLMappingVar{IndexMatcher: indexMatcher, SelectSQL: selectSQL, StructPath: structPath}
}
func (g ACLMappingVar) UsingSubfield(subfield string) ACLMappingVar {
g.Subfield = subfield
return g
}
func (ACLMappingVar) UseAs() sqltypes.Node { return ACLMappingVar{} }
func (g ACLMappingVar) ConvertVariable(rego ast.Ref) (sqltypes.Node, bool) {
// "left" will be a map of group names to actions in rego.
// {
// "all_users": ["read"]
// }
left, err := sqltypes.RegoVarPath(g.StructPath, rego)
if err != nil {
return nil, false
}
aclGrp := ACLMappingVar{
SelectSQL: g.SelectSQL,
IndexMatcher: g.IndexMatcher,
Subfield: g.Subfield,
StructPath: g.StructPath,
Source: sqltypes.RegoSource(rego.String()),
}
// We expect 1 more term. Either a ref or a string.
if len(left) != 1 {
return nil, false
}
// If the remaining is a variable, then we need to convert it.
// Assuming we support variable fields.
ref, ok := left[0].Value.(ast.Ref)
if ok && g.IndexMatcher != nil {
groupNode, ok := g.IndexMatcher.ConvertVariable(ref)
if ok {
aclGrp.GroupNode = groupNode
return aclGrp, true
}
}
// If it is a string, we assume it is a literal
groupName, ok := left[0].Value.(ast.String)
if ok {
aclGrp.GroupNode = sqltypes.String(string(groupName))
return aclGrp, true
}
// If we have not matched it yet, then it is something we do not recognize.
return nil, false
}
func (g ACLMappingVar) SQLString(cfg *sqltypes.SQLGenerator) string {
if g.Subfield != "" {
// We can't use subsequent -> operators because the first one might return
// NULL, which would result in an error like "column does not exist"' from
// the second.
return fmt.Sprintf("%s#>array[%s, '%s']", g.SelectSQL, g.GroupNode.SQLString(cfg), g.Subfield)
}
return fmt.Sprintf("%s->%s", g.SelectSQL, g.GroupNode.SQLString(cfg))
}
func (g ACLMappingVar) ContainsSQL(cfg *sqltypes.SQLGenerator, other sqltypes.Node) (string, error) {
switch other.UseAs().(type) {
// Only supports containing other strings.
case sqltypes.AstString:
return fmt.Sprintf("%s ? %s", g.SQLString(cfg), other.SQLString(cfg)), nil
default:
return "", xerrors.Errorf("unsupported acl group contains %T", other)
}
}
+22 -2
View File
@@ -193,10 +193,30 @@ func TestRegoQueries(t *testing.T) {
`"read" in input.object.acl_user_list["d5389ccc-57a4-4b13-8c3f-31747bcdc9f1"]`,
`"*" in input.object.acl_user_list["d5389ccc-57a4-4b13-8c3f-31747bcdc9f1"]`,
},
ExpectedSQL: "((user_acl->'d5389ccc-57a4-4b13-8c3f-31747bcdc9f1' ? 'read') OR " +
"(user_acl->'d5389ccc-57a4-4b13-8c3f-31747bcdc9f1' ? '*'))",
ExpectedSQL: "((user_acl->'d5389ccc-57a4-4b13-8c3f-31747bcdc9f1' ? 'read')" +
" OR (user_acl->'d5389ccc-57a4-4b13-8c3f-31747bcdc9f1' ? '*'))",
VariableConverter: regosql.DefaultVariableConverter(),
},
{
Name: "UserWorkspaceACLAllow",
Queries: []string{
`"read" in input.object.acl_user_list["d5389ccc-57a4-4b13-8c3f-31747bcdc9f1"]`,
`"*" in input.object.acl_user_list["d5389ccc-57a4-4b13-8c3f-31747bcdc9f1"]`,
},
ExpectedSQL: "((workspaces.user_acl#>array['d5389ccc-57a4-4b13-8c3f-31747bcdc9f1', 'permissions'] ? 'read')" +
" OR (workspaces.user_acl#>array['d5389ccc-57a4-4b13-8c3f-31747bcdc9f1', 'permissions'] ? '*'))",
VariableConverter: regosql.WorkspaceConverter(),
},
{
Name: "GroupWorkspaceACLAllow",
Queries: []string{
`"read" in input.object.acl_group_list["96c55a0e-73b4-44fc-abac-70d53c35c04c"]`,
`"*" in input.object.acl_group_list["96c55a0e-73b4-44fc-abac-70d53c35c04c"]`,
},
ExpectedSQL: "((workspaces.group_acl#>array['96c55a0e-73b4-44fc-abac-70d53c35c04c', 'permissions'] ? 'read')" +
" OR (workspaces.group_acl#>array['96c55a0e-73b4-44fc-abac-70d53c35c04c', 'permissions'] ? '*'))",
VariableConverter: regosql.WorkspaceConverter(),
},
{
Name: "NoACLConfig",
Queries: []string{
+18 -18
View File
@@ -14,12 +14,12 @@ func userOwnerMatcher() sqltypes.VariableMatcher {
return sqltypes.StringVarMatcher("owner_id :: text", []string{"input", "object", "owner"})
}
func groupACLMatcher(m sqltypes.VariableMatcher) sqltypes.VariableMatcher {
return ACLGroupMatcher(m, "group_acl", []string{"input", "object", "acl_group_list"})
func groupACLMatcher(m sqltypes.VariableMatcher) ACLMappingVar {
return ACLMappingMatcher(m, "group_acl", []string{"input", "object", "acl_group_list"})
}
func userACLMatcher(m sqltypes.VariableMatcher) sqltypes.VariableMatcher {
return ACLGroupMatcher(m, "user_acl", []string{"input", "object", "acl_user_list"})
func userACLMatcher(m sqltypes.VariableMatcher) ACLMappingVar {
return ACLMappingMatcher(m, "user_acl", []string{"input", "object", "acl_user_list"})
}
func TemplateConverter() *sqltypes.VariableConverter {
@@ -36,6 +36,20 @@ func TemplateConverter() *sqltypes.VariableConverter {
return matcher
}
func WorkspaceConverter() *sqltypes.VariableConverter {
matcher := sqltypes.NewVariableConverter().RegisterMatcher(
resourceIDMatcher(),
sqltypes.StringVarMatcher("workspaces.organization_id :: text", []string{"input", "object", "org_owner"}),
userOwnerMatcher(),
)
matcher.RegisterMatcher(
ACLMappingMatcher(matcher, "workspaces.group_acl", []string{"input", "object", "acl_group_list"}).UsingSubfield("permissions"),
ACLMappingMatcher(matcher, "workspaces.user_acl", []string{"input", "object", "acl_user_list"}).UsingSubfield("permissions"),
)
return matcher
}
func AuditLogConverter() *sqltypes.VariableConverter {
matcher := sqltypes.NewVariableConverter().RegisterMatcher(
resourceIDMatcher(),
@@ -81,20 +95,6 @@ func UserConverter() *sqltypes.VariableConverter {
return matcher
}
func WorkspaceConverter() *sqltypes.VariableConverter {
matcher := sqltypes.NewVariableConverter().RegisterMatcher(
resourceIDMatcher(),
sqltypes.StringVarMatcher("workspaces.organization_id :: text", []string{"input", "object", "org_owner"}),
userOwnerMatcher(),
)
matcher.RegisterMatcher(
sqltypes.AlwaysFalse(groupACLMatcher(matcher)),
sqltypes.AlwaysFalse(userACLMatcher(matcher)),
)
return matcher
}
// NoACLConverter should be used when the target SQL table does not contain
// group or user ACL columns.
func NoACLConverter() *sqltypes.VariableConverter {
+1 -1
View File
@@ -37,7 +37,7 @@ We track the following resources:
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>github_com_user_id</td><td>false</td></tr><tr><td>hashed_one_time_passcode</td><td>false</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>is_system</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>one_time_passcode_expires_at</td><td>true</td></tr><tr><td>quiet_hours_schedule</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
| WorkspaceBuild<br><i>start, stop</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>ai_task_sidebar_app_id</td><td>false</td></tr><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>has_ai_task</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_by_avatar_url</td><td>false</td></tr><tr><td>initiator_by_name</td><td>false</td></tr><tr><td>initiator_by_username</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>template_version_preset_id</td><td>false</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table> |
| WorkspaceProxy<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>created_at</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>derp_enabled</td><td>true</td></tr><tr><td>derp_only</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>region_id</td><td>true</td></tr><tr><td>token_hashed_secret</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>url</td><td>true</td></tr><tr><td>version</td><td>true</td></tr><tr><td>wildcard_hostname</td><td>true</td></tr></tbody></table> |
| WorkspaceTable<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>automatic_updates</td><td>true</td></tr><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deleting_at</td><td>true</td></tr><tr><td>dormant_at</td><td>true</td></tr><tr><td>favorite</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>next_start_at</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
| WorkspaceTable<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>automatic_updates</td><td>true</td></tr><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deleting_at</td><td>true</td></tr><tr><td>dormant_at</td><td>true</td></tr><tr><td>favorite</td><td>true</td></tr><tr><td>group_acl</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>next_start_at</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_acl</td><td>true</td></tr></tbody></table> |
<!-- End generated by 'make docs/admin/security/audit-logs.md'. -->
+2
View File
@@ -173,6 +173,8 @@ var auditableResourcesTypes = map[any]map[string]Action{
"automatic_updates": ActionTrack,
"favorite": ActionTrack,
"next_start_at": ActionTrack,
"group_acl": ActionTrack,
"user_acl": ActionTrack,
},
&database.WorkspaceBuild{}: {
"id": ActionIgnore,