mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 00:01:55 +00:00
feat: secure ctx variables in runnable inputs (#4142)
* feat: secure ctx variables * feat: add on behalf of email * oups * fix: from onBehalfOfEmail to author
This commit is contained in:
@@ -1383,7 +1383,30 @@ async fn build_args(
|
||||
|
||||
let arg_str = v.get();
|
||||
|
||||
if !arg_str.contains("\"$var:") && !arg_str.contains("\"$res:") {
|
||||
if arg_str.starts_with("\"$ctx:") {
|
||||
let prop = arg_str.trim_start_matches("\"$ctx:").trim_end_matches("\"");
|
||||
let value = match prop {
|
||||
"username" => authed.as_ref().map(|a| {
|
||||
serde_json::to_value(a.username_override.as_ref().unwrap_or(&a.username))
|
||||
}),
|
||||
"email" => authed.as_ref().map(|a| serde_json::to_value(&a.email)),
|
||||
"workspace" => Some(serde_json::to_value(&w_id)),
|
||||
"groups" => authed.as_ref().map(|a| serde_json::to_value(&a.groups)),
|
||||
"author" => Some(serde_json::to_value(&policy.on_behalf_of_email)),
|
||||
_ => {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"context variable {} not allowed",
|
||||
prop
|
||||
)))
|
||||
}
|
||||
};
|
||||
safe_args.insert(
|
||||
k.to_string(),
|
||||
to_raw_value(&value.unwrap_or(Ok(serde_json::Value::Null)).map_err(|e| {
|
||||
Error::InternalErr(format!("failed to serialize ctx variable for {}: {}", k, e))
|
||||
})?),
|
||||
);
|
||||
} else if !arg_str.contains("\"$var:") && !arg_str.contains("\"$res:") {
|
||||
safe_args.insert(k.to_string(), v);
|
||||
} else {
|
||||
safe_args.insert(
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import { userStore } from '$lib/stores'
|
||||
import { get } from 'svelte/store'
|
||||
import RefreshButton from '$lib/components/apps/components/helpers/RefreshButton.svelte'
|
||||
import { ctxRegex } from '../../utils'
|
||||
|
||||
// Component props
|
||||
export let id: string
|
||||
@@ -341,7 +342,12 @@
|
||||
allowUserResources.push(k)
|
||||
}
|
||||
} else if (field?.type == 'eval' || (field?.type == 'evalv2' && inputValues[k])) {
|
||||
nonStaticRunnableInputs[k] = await inputValues[k]?.computeExpr()
|
||||
const ctxMatch = field.expr.match(ctxRegex)
|
||||
if (ctxMatch) {
|
||||
nonStaticRunnableInputs[k] = '$ctx:' + ctxMatch[1]
|
||||
} else {
|
||||
nonStaticRunnableInputs[k] = await inputValues[k]?.computeExpr()
|
||||
}
|
||||
if (isEditor && field?.type == 'evalv2' && field.allowUserResources) {
|
||||
allowUserResources.push(k)
|
||||
}
|
||||
|
||||
@@ -363,6 +363,7 @@
|
||||
component.type === 'aggridinfinitecomponentee'
|
||||
? ['offset', 'limit', 'orderBy', 'isDesc', 'search']
|
||||
: []}
|
||||
securedContext
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
export let showOnDemandOnlyToggle = true
|
||||
export let documentationLink: string | undefined = undefined
|
||||
export let markdownTooltip: string | undefined = undefined
|
||||
export let securedContext = false
|
||||
|
||||
const { connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
@@ -185,6 +186,7 @@
|
||||
{fixedOverflowWidgets}
|
||||
{recomputeOnInputChanged}
|
||||
{showOnDemandOnlyToggle}
|
||||
{securedContext}
|
||||
/>
|
||||
{:else if componentInput?.type === 'upload'}
|
||||
<UploadInputEditor bind:componentInput {fileUpload} />
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
export let acceptSelf: boolean = false
|
||||
export let recomputeOnInputChanged = true
|
||||
export let showOnDemandOnlyToggle = false
|
||||
export let securedContext = false
|
||||
export let overridenByComponent: string[] = []
|
||||
|
||||
$: finalInputSpecsConfiguration = inputSpecsConfiguration ?? inputSpecs
|
||||
@@ -79,6 +80,7 @@
|
||||
{displayType}
|
||||
{recomputeOnInputChanged}
|
||||
{showOnDemandOnlyToggle}
|
||||
{securedContext}
|
||||
/>
|
||||
{#if deletable}
|
||||
<div class="flex flex-row-reverse -mt-4">
|
||||
|
||||
+15
-2
@@ -4,13 +4,14 @@
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext, AppViewerContext } from '$lib/components/apps/types'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import { buildExtraLib } from '$lib/components/apps/utils'
|
||||
import { buildExtraLib, ctxRegex } from '$lib/components/apps/utils'
|
||||
import { inferDeps } from '../../appUtilsInfer'
|
||||
import { Maximize2, X } from 'lucide-svelte'
|
||||
import { Maximize2, Shield, X } from 'lucide-svelte'
|
||||
import { Drawer } from '$lib/components/common'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { zIndexes } from '$lib/zIndexes'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
|
||||
export let componentInput: EvalV2AppInput | undefined
|
||||
export let id: string
|
||||
@@ -19,6 +20,7 @@
|
||||
export let acceptSelf: boolean = false
|
||||
export let recomputeOnInputChanged = true
|
||||
export let showOnDemandOnlyToggle = false
|
||||
export let securedContext = false
|
||||
|
||||
const { onchange, worldStore, state, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { evalPreview } = getContext<AppEditorContext>('AppEditorContext')
|
||||
@@ -119,6 +121,17 @@
|
||||
inferDepsFromCode(e.detail.code)
|
||||
}}
|
||||
/>
|
||||
{#if securedContext && componentInput?.expr?.match(ctxRegex)}
|
||||
<div class="border bg-surface absolute top-0.5 right-8 p-0.5">
|
||||
<Popover notClickable>
|
||||
<Shield size={12} />
|
||||
<svelte:fragment slot="text">
|
||||
This context variable is securely provided by the backend and cannot be altered by
|
||||
users
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
class="border bg-surface absolute top-0.5 right-2 p-0.5"
|
||||
on:click={() => (fullscreen = true)}><Maximize2 size={12} /></button
|
||||
|
||||
@@ -442,3 +442,5 @@ export function getImageDataURL(imageKind: string | undefined, image: string | u
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
export const ctxRegex = /^ctx\.(workspace|groups|username|email|author)$/
|
||||
|
||||
Reference in New Issue
Block a user