mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-14 00:02:55 +00:00
fix: list operators' schedule and trigger drafts under their draft rights
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7d526084a8
commit
2773bb8225
@@ -242,9 +242,31 @@ async fn test_manage_rights_admit_matching_drafts(db: Pool<Postgres>) -> anyhow:
|
||||
assert_eq!(resp.status(), 200);
|
||||
Ok(resp.json().await?)
|
||||
};
|
||||
let schedule_list_has_draft = async || -> anyhow::Result<bool> {
|
||||
let resp = c
|
||||
.get(format!("{api}/schedules/list?include_draft_only=true"))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
let rows: Vec<serde_json::Value> = resp.json().await?;
|
||||
Ok(rows.iter().any(|r| r["path"] == "u/operator/sched_draft"))
|
||||
};
|
||||
|
||||
let (status, body) = save("trigger_schedule", "u/operator/sched_draft").await?;
|
||||
assert_eq!(status, 200, "{body}");
|
||||
let resp = c
|
||||
.post(format!("{api}/drafts/update/trigger_schedule/u/operator/sched_draft"))
|
||||
.json(&json!({ "value": {
|
||||
"path": "u/operator/sched_draft",
|
||||
"schedule": "0 0 12 * * *",
|
||||
"timezone": "UTC",
|
||||
"script_path": "f/system/hello",
|
||||
"is_flow": false,
|
||||
"args": {},
|
||||
"enabled": false
|
||||
} }))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(resp.status(), 200, "{}", resp.text().await?);
|
||||
assert!(schedule_list_has_draft().await?);
|
||||
let (status, body) = save("trigger_http", "u/operator/http_draft").await?;
|
||||
assert_eq!(status, 200, "{body}");
|
||||
// The rights name schedules and triggers, nothing else.
|
||||
@@ -260,6 +282,8 @@ async fn test_manage_rights_admit_matching_drafts(db: Pool<Postgres>) -> anyhow:
|
||||
);
|
||||
let (status, body) = save("trigger_schedule", "u/operator/sched_draft").await?;
|
||||
assert_eq!(status, 403, "{body}");
|
||||
// The list follows the right too, as the app list follows `builder_apps`.
|
||||
assert!(!schedule_list_has_draft().await?);
|
||||
let (status, body) = save("trigger_http", "u/operator/http_draft").await?;
|
||||
assert_eq!(status, 200, "{body}");
|
||||
|
||||
|
||||
@@ -916,7 +916,6 @@ async fn list_schedule(
|
||||
|
||||
// Append the authed user's draft-only schedules; see scripts.rs.
|
||||
if lsq.include_draft_only.unwrap_or(false)
|
||||
&& !authed.is_operator
|
||||
&& offset == 0
|
||||
&& lsq.path.is_none()
|
||||
&& lsq.is_flow.is_none()
|
||||
@@ -927,6 +926,13 @@ async fn list_schedule(
|
||||
&& lsq.summary.is_none()
|
||||
&& lsq.broad_filter.is_none()
|
||||
&& lsq.label.is_none()
|
||||
&& (!authed.is_operator
|
||||
|| windmill_common::workspaces::operator_can_draft(
|
||||
&db,
|
||||
&w_id,
|
||||
Some(UserDraftItemKind::TriggerSchedule),
|
||||
)
|
||||
.await?)
|
||||
{
|
||||
let draft_only_rows = fetch_draft_only_list_rows(
|
||||
&db,
|
||||
|
||||
@@ -695,12 +695,18 @@ async fn list_triggers<T: TriggerCrud>(
|
||||
// Best-effort: the editor's TriggerData shape overlaps T::Trigger but a per-kind
|
||||
// config can deviate, so drop a row on deserialize failure rather than fail the list.
|
||||
if query.include_draft_only.unwrap_or(false)
|
||||
&& !authed.is_operator
|
||||
&& query.page.unwrap_or(0) == 0
|
||||
&& query.path.is_none()
|
||||
&& query.is_flow.is_none()
|
||||
&& query.path_start.is_none()
|
||||
&& query.label.is_none()
|
||||
&& (!authed.is_operator
|
||||
|| windmill_common::workspaces::operator_can_draft(
|
||||
&db,
|
||||
&workspace_id,
|
||||
Some(T::user_draft_item_kind()),
|
||||
)
|
||||
.await?)
|
||||
{
|
||||
let draft_only_rows = fetch_draft_only_list_rows(
|
||||
&db,
|
||||
|
||||
@@ -65,6 +65,10 @@ stay refused even though operators edit those items directly. The AI session cha
|
||||
schedules and triggers only as drafts and then deploys them, so without this rule an operator who
|
||||
may create a schedule on its page could not ask the chat for one.
|
||||
|
||||
The schedule and trigger list routes append an operator's draft-only rows under the same
|
||||
`operator_can_draft` check, so a saved draft shows on its list page exactly while the operator may
|
||||
still save it.
|
||||
|
||||
Deploying a draft goes through the ordinary create and update routes, so the router gate is still
|
||||
what decides the write. The draft rule only has to agree with it: a draft the gate would refuse to
|
||||
deploy should not be saved in the first place. The frontend mirrors the rule in `roleCanDraft`
|
||||
|
||||
Reference in New Issue
Block a user