mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 08:07:03 +00:00
fix(datatables): a resource-backed clone is not a copy, and the two helpers state their contract
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT ws.workspace_id AS \"workspace_id!\", dt.key AS \"name!\", w.deleted AS \"deleted!\"\n FROM workspace_settings ws\n JOIN workspace w ON w.id = ws.workspace_id,\n jsonb_each(ws.datatable->'datatables') dt\n WHERE ws.workspace_id = ANY($1)\n AND dt.value->'database' = $2\n ORDER BY ws.workspace_id, dt.key",
|
||||
"query": "SELECT ws.workspace_id AS \"workspace_id!\", dt.key AS \"name!\", w.deleted AS \"deleted!\"\n FROM workspace_settings ws\n JOIN workspace w ON w.id = ws.workspace_id,\n jsonb_each(ws.datatable->'datatables') dt\n WHERE ws.workspace_id = ANY($1)\n AND dt.value->'database' = $2\n AND dt.value->'forked_from' IS NULL\n ORDER BY ws.workspace_id, dt.key",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -31,5 +31,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "aab1b338605f4fb9dd0d19e78d32849ccc783d295de38508d20ec30f99f518ca"
|
||||
"hash": "34a6b159360d652fac09662cedab4a490be9db7e331874cbfa7b1b93e50af586"
|
||||
}
|
||||
@@ -125,7 +125,8 @@ async fn enabling_permissions_is_refused_while_a_fork_exists(
|
||||
)
|
||||
.bind(json!({
|
||||
"datatables": {
|
||||
"main": { "database": { "resource_type": "instance", "resource_path": "dt_main" } }
|
||||
"main": { "database": { "resource_type": "instance", "resource_path": "dt_main" } },
|
||||
"byo": { "database": { "resource_type": "postgresql", "resource_path": "u/test-user/pg" } }
|
||||
}
|
||||
}))
|
||||
.execute(&db)
|
||||
@@ -141,7 +142,8 @@ async fn enabling_permissions_is_refused_while_a_fork_exists(
|
||||
)
|
||||
.bind(json!({
|
||||
"datatables": {
|
||||
"main": { "database": { "resource_type": "instance", "resource_path": "dt_main" } }
|
||||
"main": { "database": { "resource_type": "instance", "resource_path": "dt_main" } },
|
||||
"byo": { "database": { "resource_type": "postgresql", "resource_path": "u/test-user/pg" } }
|
||||
}
|
||||
}))
|
||||
.execute(&db)
|
||||
@@ -199,6 +201,30 @@ async fn enabling_permissions_is_refused_while_a_fork_exists(
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
// A resource-backed copy keeps the parent's pointer when cloned — the cloned
|
||||
// resource is what changes — so `forked_from` is what tells the two apart.
|
||||
let byo = format!("{ws}/workspaces/datatable_permissions/byo/preview");
|
||||
let resp = authed(client().post(&byo), "SECRET_TOKEN")
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(resp.status(), 400);
|
||||
let text = resp.text().await?;
|
||||
assert!(text.contains("wm-fork-t (data table 'byo')"), "{text}");
|
||||
sqlx::query(
|
||||
r#"UPDATE workspace_settings
|
||||
SET datatable = jsonb_set(datatable, '{datatables,byo,forked_from}', '{"schema": {}}')
|
||||
WHERE workspace_id = 'wm-fork-t'"#,
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
let resp = authed(client().post(&byo), "SECRET_TOKEN")
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?;
|
||||
let text = resp.text().await?;
|
||||
assert!(!text.contains("cannot be enabled"), "{text}");
|
||||
|
||||
// Archiving keeps the fork's members and its copy, so it still counts; a fork
|
||||
// whose copy is a clone of its own does not.
|
||||
sqlx::query("UPDATE workspace SET deleted = true WHERE id = 'wm-fork-t'")
|
||||
|
||||
@@ -788,7 +788,10 @@ async fn refuse_enabling_permissions_over_shared_access(
|
||||
};
|
||||
let forks = windmill_common::workspaces::list_fork_descendants(db, w_id).await?;
|
||||
if !forks.is_empty() {
|
||||
// A clone points at a database of its own and does not count.
|
||||
// A clone (`forked_from`) points at a database of its own and does not
|
||||
// count. It is not told apart by the pointer alone: cloning a
|
||||
// resource-backed data table rewrites the cloned resource, not the path
|
||||
// the entry names, so the pointer still equals the parent's.
|
||||
let copies = sqlx::query!(
|
||||
r#"SELECT ws.workspace_id AS "workspace_id!", dt.key AS "name!", w.deleted AS "deleted!"
|
||||
FROM workspace_settings ws
|
||||
@@ -796,6 +799,7 @@ async fn refuse_enabling_permissions_over_shared_access(
|
||||
jsonb_each(ws.datatable->'datatables') dt
|
||||
WHERE ws.workspace_id = ANY($1)
|
||||
AND dt.value->'database' = $2
|
||||
AND dt.value->'forked_from' IS NULL
|
||||
ORDER BY ws.workspace_id, dt.key"#,
|
||||
&forks[..],
|
||||
database,
|
||||
|
||||
@@ -3146,6 +3146,10 @@ pub(crate) async fn is_instance_datatable(db: &DB, w_id: &str, name: &str) -> Re
|
||||
/// data the roles existed to divide. Reproducing the roles in the copy is a
|
||||
/// separate piece of work; until it exists, a fork goes without the data table
|
||||
/// (the fork creation leaves a permissioned one out of the fork's config).
|
||||
///
|
||||
/// Authorization: performs none. It reads whether `w_id`'s data table is
|
||||
/// permissioned, for any `w_id` it is handed, so callers MUST already have
|
||||
/// authorized the caller for that workspace.
|
||||
pub(crate) async fn refuse_clone_of_permissioned_datatable(
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
|
||||
@@ -2166,7 +2166,10 @@ async fn is_workspace_owner(
|
||||
/// `parent_workspace_id`, but a `wm-fork-` workspace can outlive its parent (the FK is
|
||||
/// `ON DELETE SET NULL`), so also treat the prefix as fork-ness — otherwise an orphaned fork would
|
||||
/// lose owner-self-delete. Used to gate owner-self-delete, which is permitted for forks/dev
|
||||
/// workspaces but requires superadmin otherwise.
|
||||
/// workspaces but requires superadmin otherwise, and to refuse a data table opt-in from a fork.
|
||||
///
|
||||
/// Authorization: performs none. It reads the lineage of any `w_id` it is handed, so callers
|
||||
/// MUST already have authorized the caller for that workspace.
|
||||
pub(crate) async fn workspace_is_fork(db: &DB, w_id: &str) -> Result<bool> {
|
||||
if w_id.starts_with(WM_FORK_PREFIX) {
|
||||
return Ok(true);
|
||||
|
||||
Reference in New Issue
Block a user