mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 00:04:10 +00:00
fix: reject unsupported kinds in move_draft, survive NUL-poisoned draft rows
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5b806a5e3c
commit
c6bf9ca965
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE draft AS d\n SET path = $3,\n value = CASE\n WHEN position(chr(92) || 'u0000' in d.value::text) > 0 THEN d.value\n WHEN to_jsonb(d.value) -> $4::text = to_jsonb($2::text)\n THEN to_json(jsonb_set(to_jsonb(d.value), ARRAY[$4::text], to_jsonb($3::text), false))\n ELSE d.value\n END\n WHERE d.workspace_id = $1\n AND d.path = $2\n AND d.typ::text = ANY($5::text[])\n AND NOT EXISTS (\n SELECT 1 FROM draft o\n WHERE o.workspace_id = d.workspace_id\n AND o.path = $3\n AND o.typ = d.typ\n AND o.email IS NOT DISTINCT FROM d.email\n )\n RETURNING d.id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "666eea2c3d584885923d86b296e197c9249a79402742e0345a7fdf5f43843917"
|
||||
}
|
||||
@@ -760,6 +760,23 @@ async fn move_draft(
|
||||
) -> Result<String> {
|
||||
let path = path.to_path();
|
||||
let new_path = req.new_path.as_str();
|
||||
// Only the full-page editor kinds. The in-value rewrite below keys off
|
||||
// `typed_path_field()`, which answers `draft_path` for every non-script kind
|
||||
// — true for flows and apps, wrong for resources, variables and triggers,
|
||||
// whose drafts keep their deploy target in `value.path`. Moving one of those
|
||||
// would inject a `draft_path` nothing reads and leave the real target naming
|
||||
// the old location, so the next deploy would recreate it where it came from.
|
||||
if !matches!(
|
||||
kind,
|
||||
UserDraftItemKind::Script
|
||||
| UserDraftItemKind::Flow
|
||||
| UserDraftItemKind::App
|
||||
| UserDraftItemKind::RawApp
|
||||
) {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"moving a {kind:?} draft is not supported — only scripts, flows and apps"
|
||||
)));
|
||||
}
|
||||
// A summary-only edit is a legitimate use of this endpoint: the drawer edits
|
||||
// both fields, and for a draft-only script the path it posts back is the row
|
||||
// path unchanged (`list_scripts` only reports `draft_path` when it differs).
|
||||
|
||||
@@ -625,15 +625,21 @@ pub async fn move_drafts_for_path(
|
||||
// this draft would send the item back where it came from.
|
||||
// - anything else: a rename the user staged in their editor. Deploying
|
||||
// should still land there, so the move leaves it alone.
|
||||
//
|
||||
// `draft.value` is a `json` column, so `to_jsonb` raises 22P05 on a row that
|
||||
// still carries a NUL escape from before the write-time `strip_json_nul`
|
||||
// guard (see `backend/tests/drafts_nul.rs`). Such a row is skipped by the
|
||||
// rewrite and carried on its `path` column alone: one teammate's poisoned
|
||||
// draft must not abort an unrelated user's rename mid-transaction.
|
||||
let moved = sqlx::query_scalar!(
|
||||
r#"UPDATE draft AS d
|
||||
SET path = $3,
|
||||
value = to_json(
|
||||
CASE WHEN to_jsonb(d.value) -> $4::text = to_jsonb($2::text)
|
||||
THEN jsonb_set(to_jsonb(d.value), ARRAY[$4::text], to_jsonb($3::text), false)
|
||||
ELSE to_jsonb(d.value)
|
||||
END
|
||||
)
|
||||
value = CASE
|
||||
WHEN position(chr(92) || 'u0000' in d.value::text) > 0 THEN d.value
|
||||
WHEN to_jsonb(d.value) -> $4::text = to_jsonb($2::text)
|
||||
THEN to_json(jsonb_set(to_jsonb(d.value), ARRAY[$4::text], to_jsonb($3::text), false))
|
||||
ELSE d.value
|
||||
END
|
||||
WHERE d.workspace_id = $1
|
||||
AND d.path = $2
|
||||
AND d.typ::text = ANY($5::text[])
|
||||
|
||||
Reference in New Issue
Block a user