mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-12 16:02:56 +00:00
fix: apply token scopes to the runnables activity endpoint, refresh the owner-chips comment
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
7ab66ba097
commit
aa97c53180
@@ -1149,26 +1149,28 @@ struct RunnablesActivityResponse {
|
||||
/// The latest runs and the attached triggers of a batch of scripts and flows, for
|
||||
/// the homepage rows. Only paths the caller can see as a deployed script or flow
|
||||
/// are answered: the `visible` CTE runs under RLS, and `native_trigger`, which has
|
||||
/// no RLS, is only reached through it.
|
||||
/// no RLS, is only reached through it. RLS doesn't honor token scopes, so a path
|
||||
/// outside the token's `scripts:read` / `flows:read` grant is dropped first.
|
||||
async fn get_runnables_activity(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(req): Json<RunnablesActivityRequest>,
|
||||
) -> JsonResult<RunnablesActivityResponse> {
|
||||
let mut paths: Vec<String> = vec![];
|
||||
let mut is_flows: Vec<bool> = vec![];
|
||||
for (list, is_flow) in [(req.scripts, false), (req.flows, true)] {
|
||||
for p in list {
|
||||
paths.push(p);
|
||||
is_flows.push(is_flow);
|
||||
}
|
||||
}
|
||||
if paths.len() > ACTIVITY_MAX_PATHS {
|
||||
if req.scripts.len() + req.flows.len() > ACTIVITY_MAX_PATHS {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"at most {ACTIVITY_MAX_PATHS} paths per request"
|
||||
)));
|
||||
}
|
||||
let mut paths: Vec<String> = vec![];
|
||||
let mut is_flows: Vec<bool> = vec![];
|
||||
for (list, is_flow, domain) in [(req.scripts, false, "scripts"), (req.flows, true, "flows")] {
|
||||
let scope = build_scope_path_filter(&authed, domain, "read");
|
||||
for p in list.into_iter().filter(|p| scope.allows(p)) {
|
||||
paths.push(p);
|
||||
is_flows.push(is_flow);
|
||||
}
|
||||
}
|
||||
let mut res = RunnablesActivityResponse::default();
|
||||
if paths.is_empty() {
|
||||
return Ok(Json(res));
|
||||
|
||||
@@ -2055,8 +2055,8 @@
|
||||
</div>
|
||||
</div>
|
||||
{#if !contentActive && hasChips}
|
||||
<!-- Owner and label chips on one line. Each function binding routes the chip's
|
||||
selection into the searchbar key of the same name, and `queryName` points
|
||||
<!-- Owner chips. The function binding routes the selected chip into the
|
||||
searchbar's `owner` key, and `queryName` points
|
||||
ListFilters' own mount-time URL read at the param the filter instance syncs, so
|
||||
the two writers agree. No `syncQuery`: the filter instance owns the URL. -->
|
||||
<div class="gap-2 w-full flex flex-wrap mt-3">
|
||||
@@ -2234,7 +2234,10 @@
|
||||
variant="subtle"
|
||||
wrapperClasses="w-fit"
|
||||
disabled={filter !== ''}
|
||||
btnClasses={twMerge('-ml-1.5 px-1.5 text-2xs font-medium whitespace-nowrap', active ? 'text-primary' : 'text-secondary')}
|
||||
btnClasses={twMerge(
|
||||
'-ml-1.5 px-1.5 text-2xs font-medium whitespace-nowrap',
|
||||
active ? 'text-primary' : 'text-secondary'
|
||||
)}
|
||||
endIcon={active ? { icon: sortOrder === asc ? ArrowUp : ArrowDown } : undefined}
|
||||
title={filter !== ''
|
||||
? 'Sorting is disabled while searching (results are ranked by relevance)'
|
||||
|
||||
Reference in New Issue
Block a user