mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 00:05:27 +00:00
fix(datatables): the clone stamp is server-owned, and a kept original never carries one
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE workspace_settings\n SET datatable = jsonb_set(datatable, '{datatables}', (\n SELECT COALESCE(jsonb_object_agg(\n key,\n CASE WHEN key = ANY($2) THEN value - 'permissions' ELSE value END\n ), '{}'::jsonb)\n FROM jsonb_each(datatable->'datatables')\n WHERE key = ANY($2)\n OR COALESCE((value->'permissions'->>'enabled')::boolean, false) = false\n ))\n WHERE workspace_id = $1 AND jsonb_typeof(datatable->'datatables') = 'object'",
|
||||
"query": "UPDATE workspace_settings\n SET datatable = jsonb_set(datatable, '{datatables}', (\n SELECT COALESCE(jsonb_object_agg(\n key,\n CASE WHEN key = ANY($2) THEN value - 'permissions' ELSE value - 'forked_from' END\n ), '{}'::jsonb)\n FROM jsonb_each(datatable->'datatables')\n WHERE key = ANY($2)\n OR COALESCE((value->'permissions'->>'enabled')::boolean, false) = false\n ))\n WHERE workspace_id = $1 AND jsonb_typeof(datatable->'datatables') = 'object'",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -11,5 +11,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7637d311391ab08865b10f69d048583f43c8157abf675358cfd84ff056925fc4"
|
||||
"hash": "77841f6a74d13f92b095a5fdeca59d6b5e01e21890ef53ccb600c2f3506d84a3"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
7765fc05fdb287f0c258f51c7029b1abc3627b6c
|
||||
f89f0cd16ec24f7beb146ba8373d688e1fe126e8
|
||||
|
||||
@@ -218,6 +218,8 @@ async fn enabling_permissions_is_refused_while_a_fork_exists(
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
// Past the refusal the preview fails on the resource, which this test does
|
||||
// not have; the refusal is what is pinned.
|
||||
let resp = authed(client().post(&byo), "SECRET_TOKEN")
|
||||
.json(&body)
|
||||
.send()
|
||||
@@ -404,3 +406,86 @@ async fn a_rename_leaves_no_datatable_under_the_old_id(db: Pool<Postgres>) -> an
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A fork that keeps the original copies the parent's entry verbatim, `forked_from`
|
||||
/// included when the parent's own entry is a clone (a detached dev workspace keeps
|
||||
/// its clones). The stamp means "cloned into this workspace's own database", and
|
||||
/// the opt-in trusts it, so a copy must not carry one.
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn a_kept_original_does_not_inherit_the_clone_stamp(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
let ws = format!("http://localhost:{port}/api/w/test-workspace");
|
||||
|
||||
sqlx::query(
|
||||
r#"UPDATE workspace_settings SET datatable = $1 WHERE workspace_id = 'test-workspace'"#,
|
||||
)
|
||||
.bind(json!({
|
||||
"datatables": {
|
||||
"byo": {
|
||||
"database": { "resource_type": "postgresql", "resource_path": "u/test-user/pg" },
|
||||
"forked_from": { "schema": {} }
|
||||
}
|
||||
}
|
||||
}))
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{ws}/workspaces/create_fork")),
|
||||
"SECRET_TOKEN",
|
||||
)
|
||||
.json(&json!({ "id": "wm-fork-kept", "name": "kept" }))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(resp.status(), 200, "{}", resp.text().await?);
|
||||
|
||||
let copy: serde_json::Value = sqlx::query_scalar(
|
||||
"SELECT datatable->'datatables'->'byo' FROM workspace_settings WHERE workspace_id = 'wm-fork-kept'",
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert_eq!(copy["database"]["resource_path"], json!("u/test-user/pg"));
|
||||
assert!(copy.get("forked_from").is_none(), "{copy}");
|
||||
|
||||
let resp = authed(
|
||||
client().post(format!("{ws}/workspaces/datatable_permissions/byo/preview")),
|
||||
"SECRET_TOKEN",
|
||||
)
|
||||
.json(&json!({ "enabled": true, "roles": [] }))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(resp.status(), 400);
|
||||
let text = resp.text().await?;
|
||||
assert!(text.contains("wm-fork-kept (data table 'byo')"), "{text}");
|
||||
|
||||
// The form cannot stamp the copy either.
|
||||
let resp = authed(
|
||||
client().post(format!(
|
||||
"http://localhost:{port}/api/w/wm-fork-kept/workspaces/edit_datatable_config"
|
||||
)),
|
||||
"SECRET_TOKEN",
|
||||
)
|
||||
.json(&json!({
|
||||
"settings": { "datatables": {
|
||||
"byo": {
|
||||
"database": { "resource_type": "postgresql", "resource_path": "u/test-user/pg" },
|
||||
"forked_from": { "schema": {} }
|
||||
}
|
||||
}}
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(resp.status(), 200, "{}", resp.text().await?);
|
||||
let copy: serde_json::Value = sqlx::query_scalar(
|
||||
"SELECT datatable->'datatables'->'byo' FROM workspace_settings WHERE workspace_id = 'wm-fork-kept'",
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
assert!(copy.get("forked_from").is_none(), "{copy}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3863,6 +3863,19 @@ async fn edit_datatable_config(
|
||||
// Same for permissions, owned by the datatable_permissions endpoints.
|
||||
let old = old_datatables.get(lookup);
|
||||
dt.permissions = old.and_then(|old| old.permissions.clone());
|
||||
// `forked_from` is stamped by the fork clone and read by the permissions
|
||||
// opt-in as "this entry has a database of its own": the form may update the
|
||||
// schema snapshot inside it, never add or remove the stamp.
|
||||
dt.forked_from = match (
|
||||
old.and_then(|old| old.forked_from.as_ref()),
|
||||
dt.forked_from.take(),
|
||||
) {
|
||||
(None, _) => None,
|
||||
(Some(_), Some(new)) => Some(new),
|
||||
(Some(old), None) => Some(windmill_common::workspaces::DataTableForkedFrom {
|
||||
schema: old.schema.clone(),
|
||||
}),
|
||||
};
|
||||
// The roles live in the database this data table points at: their logins
|
||||
// were created there and every grant they hold is recorded there. Carried
|
||||
// onto another database they authenticate against a cluster that never
|
||||
@@ -8371,6 +8384,10 @@ async fn create_workspace_fork(
|
||||
// would never reach the copy, and the fork would keep running as the role it named. So
|
||||
// a permissioned data table is not shared into a fork at all; the fork can fork it, or
|
||||
// go without it.
|
||||
//
|
||||
// A copy that was not forked here also loses any `forked_from` it inherited: the stamp
|
||||
// means "cloned into this workspace's own database", which is what the permissions
|
||||
// opt-in reads it as, and a copy of the parent's clone points where the parent points.
|
||||
let forked_datatable_names: Vec<String> = nw
|
||||
.forked_datatables
|
||||
.iter()
|
||||
@@ -8381,7 +8398,7 @@ async fn create_workspace_fork(
|
||||
SET datatable = jsonb_set(datatable, '{datatables}', (
|
||||
SELECT COALESCE(jsonb_object_agg(
|
||||
key,
|
||||
CASE WHEN key = ANY($2) THEN value - 'permissions' ELSE value END
|
||||
CASE WHEN key = ANY($2) THEN value - 'permissions' ELSE value - 'forked_from' END
|
||||
), '{}'::jsonb)
|
||||
FROM jsonb_each(datatable->'datatables')
|
||||
WHERE key = ANY($2)
|
||||
|
||||
Reference in New Issue
Block a user