Add logs for when permissioned as is dealt with

This commit is contained in:
wendrul
2026-03-09 18:19:32 +01:00
parent 50a945f9c8
commit 937555f876
7 changed files with 61 additions and 53 deletions
+7 -5
View File
@@ -18,7 +18,7 @@ import newCommand from "./new.ts";
import generateAgentsCommand from "./generate_agents.ts";
import { isVersionsGeq1585 } from "../sync/global.ts";
import type { PermissionedAsContext } from "../../core/permissioned_as.ts";
import { resolvePermissionedAsEmail, lookupUsernameByEmail } from "../../core/permissioned_as.ts";
import { resolvePermissionedAsRule, lookupUsernameByEmail } from "../../core/permissioned_as.ts";
export interface AppFile {
value: any;
@@ -165,26 +165,28 @@ export async function pushApp(
(localApp.policy as any).on_behalf_of = remoteOnBehalfOf;
(localApp.policy as any).on_behalf_of_email = remoteOnBehalfOfEmail;
preserveFields.preserve_on_behalf_of = true;
log.info(`Preserving ${remoteOnBehalfOfEmail ?? remoteOnBehalfOf} as permissioned_as for app ${remotePath}`);
}
} else {
// Creating: apply defaultPermissionedAs rule if one matches
const ruleEmail = resolvePermissionedAsEmail(
const rule = resolvePermissionedAsRule(
remotePath,
permissionedAsContext.rules
);
if (ruleEmail) {
if (rule) {
// Set both on_behalf_of and on_behalf_of_email on the policy
// The backend requires on_behalf_of to be set for preserve to work
if (localApp.policy) {
const username = await lookupUsernameByEmail(
workspace,
ruleEmail,
rule.email,
permissionedAsContext.emailToUsernameCache
);
(localApp.policy as any).on_behalf_of = `u/${username}`;
(localApp.policy as any).on_behalf_of_email = ruleEmail;
(localApp.policy as any).on_behalf_of_email = rule.email;
}
preserveFields.preserve_on_behalf_of = true;
log.info(`Setting app ${remotePath} to run permissioned as ${rule.email} (matched rule '${rule.path_pattern}' in wmill.yaml)`);
}
}
}
+6 -4
View File
@@ -21,7 +21,7 @@ import { Flow, OpenFlowWPath } from "../../../gen/types.gen.ts";
import { replaceInlineScripts } from "../../../windmill-utils-internal/src/inline-scripts/replacer.ts";
import { generateFlowLockInternal } from "./flow_metadata.ts";
import type { PermissionedAsContext } from "../../core/permissioned_as.ts";
import { resolvePermissionedAsEmail } from "../../core/permissioned_as.ts";
import { resolvePermissionedAsRule } from "../../core/permissioned_as.ts";
export interface FlowFile {
summary: string;
@@ -76,16 +76,18 @@ export async function pushFlow(
if (flow.on_behalf_of_email) {
preserveFields.on_behalf_of_email = flow.on_behalf_of_email;
preserveFields.preserve_on_behalf_of = true;
log.info(`Preserving ${flow.on_behalf_of_email} as permissioned_as for flow ${remotePath}`);
}
} else {
// Creating: apply defaultPermissionedAs rule if one matches
const ruleEmail = resolvePermissionedAsEmail(
const rule = resolvePermissionedAsRule(
remotePath,
permissionedAsContext.rules
);
if (ruleEmail) {
preserveFields.on_behalf_of_email = ruleEmail;
if (rule) {
preserveFields.on_behalf_of_email = rule.email;
preserveFields.preserve_on_behalf_of = true;
log.info(`Setting flow ${remotePath} to run permissioned as ${rule.email} (matched rule '${rule.path_pattern}' in wmill.yaml)`);
}
}
}
+6 -4
View File
@@ -19,7 +19,7 @@ import {
import { Schedule } from "../../../gen/types.gen.ts";
import type { PermissionedAsContext } from "../../core/permissioned_as.ts";
import {
resolvePermissionedAsEmail,
resolvePermissionedAsRule,
lookupUsernameByEmail,
} from "../../core/permissioned_as.ts";
@@ -126,21 +126,23 @@ export async function pushSchedule(
preserveFields.preserve_email = true;
if ((schedule as Schedule).edited_by) {
preserveFields.email = (schedule as Schedule).edited_by;
log.info(`Preserving ${(schedule as Schedule).edited_by} as permissioned_as for schedule ${path}`);
}
} else {
// Creating: apply defaultPermissionedAs rule if one matches
const ruleEmail = resolvePermissionedAsEmail(
const rule = resolvePermissionedAsRule(
path,
permissionedAsContext.rules
);
if (ruleEmail) {
if (rule) {
const username = await lookupUsernameByEmail(
workspace,
ruleEmail,
rule.email,
permissionedAsContext.emailToUsernameCache
);
preserveFields.email = username;
preserveFields.preserve_email = true;
log.info(`Setting schedule ${path} to run permissioned as ${rule.email} (matched rule '${rule.path_pattern}' in wmill.yaml)`);
}
}
}
+6 -4
View File
@@ -53,7 +53,7 @@ import { createTarBlob, type TarEntry } from "../../utils/tar.ts";
import { execSync } from "node:child_process";
import { NewScript, Script } from "../../../gen/types.gen.ts";
import type { PermissionedAsContext } from "../../core/permissioned_as.ts";
import { resolvePermissionedAsEmail } from "../../core/permissioned_as.ts";
import { resolvePermissionedAsRule } from "../../core/permissioned_as.ts";
import {
isRawAppBackendPath as isRawAppBackendPathInternal,
isAppInlineScriptPath as isAppInlineScriptPathInternal,
@@ -433,16 +433,18 @@ export async function handleFile(
if (remote.on_behalf_of_email) {
requestBodyCommon.on_behalf_of_email = remote.on_behalf_of_email;
requestBodyCommon.preserve_on_behalf_of = true;
log.info(`Preserving ${remote.on_behalf_of_email} as permissioned_as for script ${remotePath}`);
}
} else {
// Creating: apply defaultPermissionedAs rule if one matches
const ruleEmail = resolvePermissionedAsEmail(
const rule = resolvePermissionedAsRule(
remotePath,
permissionedAsContext.rules
);
if (ruleEmail) {
requestBodyCommon.on_behalf_of_email = ruleEmail;
if (rule) {
requestBodyCommon.on_behalf_of_email = rule.email;
requestBodyCommon.preserve_on_behalf_of = true;
log.info(`Setting script ${remotePath} to run permissioned as ${rule.email} (matched rule '${rule.path_pattern}' in wmill.yaml)`);
}
}
}
+6 -4
View File
@@ -38,7 +38,7 @@ import { requireLogin } from "../../core/auth.ts";
import { validatePath, resolveWorkspace } from "../../core/context.ts";
import type { PermissionedAsContext } from "../../core/permissioned_as.ts";
import {
resolvePermissionedAsEmail,
resolvePermissionedAsRule,
lookupUsernameByEmail,
} from "../../core/permissioned_as.ts";
@@ -175,21 +175,23 @@ export async function pushTrigger<K extends TriggerType>(
preserveFields.preserve_email = true;
if ((trigger as any).edited_by) {
preserveFields.email = (trigger as any).edited_by;
log.info(`Preserving ${(trigger as any).edited_by} as permissioned_as for trigger ${path}`);
}
} else {
// Creating: apply defaultPermissionedAs rule if one matches
const ruleEmail = resolvePermissionedAsEmail(
const rule = resolvePermissionedAsRule(
path,
permissionedAsContext.rules
);
if (ruleEmail) {
if (rule) {
const username = await lookupUsernameByEmail(
workspace,
ruleEmail,
rule.email,
permissionedAsContext.emailToUsernameCache
);
preserveFields.email = username;
preserveFields.preserve_email = true;
log.info(`Setting trigger ${path} to run permissioned as ${rule.email} (matched rule '${rule.path_pattern}' in wmill.yaml)`);
}
}
}
+5 -5
View File
@@ -84,16 +84,16 @@ export function validatePermissionedAsRules(
}
/**
* Resolves which email should be used for a new item based on defaultPermissionedAs rules.
* Returns undefined if no rule matches.
* Resolves which rule should be used for a new item based on defaultPermissionedAs rules.
* Returns the matching rule, or undefined if no rule matches.
*/
export function resolvePermissionedAsEmail(
export function resolvePermissionedAsRule(
path: string,
rules: PermissionedAsRule[]
): string | undefined {
): PermissionedAsRule | undefined {
for (const rule of rules) {
if (minimatch(path, rule.path_pattern)) {
return rule.email;
return rule;
}
}
return undefined;
+25 -27
View File
@@ -5,7 +5,7 @@
import { expect, test, describe } from "bun:test";
import {
validatePermissionedAsRules,
resolvePermissionedAsEmail,
resolvePermissionedAsRule,
type PermissionedAsRule,
} from "../src/core/permissioned_as.ts";
@@ -203,59 +203,58 @@ describe("validatePermissionedAsRules", () => {
});
// =============================================================================
// resolvePermissionedAsEmail
// resolvePermissionedAsRule
// =============================================================================
describe("resolvePermissionedAsEmail", () => {
describe("resolvePermissionedAsRule", () => {
const rules: PermissionedAsRule[] = [
{ email: "prod@company.com", path_pattern: "f/production/**" },
{ email: "staging@company.com", path_pattern: "f/staging/**" },
{ email: "default@company.com", path_pattern: "f/**" },
];
test("returns matching email for specific path", () => {
expect(
resolvePermissionedAsEmail("f/production/my_script", rules)
).toBe("prod@company.com");
test("returns matching rule for specific path", () => {
const rule = resolvePermissionedAsRule("f/production/my_script", rules);
expect(rule?.email).toBe("prod@company.com");
expect(rule?.path_pattern).toBe("f/production/**");
});
test("returns first matching rule (production over default)", () => {
expect(
resolvePermissionedAsEmail("f/production/deep/nested", rules)
).toBe("prod@company.com");
const rule = resolvePermissionedAsRule("f/production/deep/nested", rules);
expect(rule?.email).toBe("prod@company.com");
expect(rule?.path_pattern).toBe("f/production/**");
});
test("returns staging email for staging path", () => {
expect(
resolvePermissionedAsEmail("f/staging/my_flow", rules)
).toBe("staging@company.com");
test("returns staging rule for staging path", () => {
const rule = resolvePermissionedAsRule("f/staging/my_flow", rules);
expect(rule?.email).toBe("staging@company.com");
expect(rule?.path_pattern).toBe("f/staging/**");
});
test("falls through to default rule", () => {
expect(
resolvePermissionedAsEmail("f/other/my_script", rules)
).toBe("default@company.com");
const rule = resolvePermissionedAsRule("f/other/my_script", rules);
expect(rule?.email).toBe("default@company.com");
expect(rule?.path_pattern).toBe("f/**");
});
test("returns undefined when no rule matches", () => {
expect(
resolvePermissionedAsEmail("u/admin/my_script", rules)
resolvePermissionedAsRule("u/admin/my_script", rules)
).toBeUndefined();
});
test("returns undefined for empty rules", () => {
expect(resolvePermissionedAsEmail("f/anything", [])).toBeUndefined();
expect(resolvePermissionedAsRule("f/anything", [])).toBeUndefined();
});
test("handles exact path patterns", () => {
const exactRules: PermissionedAsRule[] = [
{ email: "exact@company.com", path_pattern: "f/specific/script" },
];
const rule = resolvePermissionedAsRule("f/specific/script", exactRules);
expect(rule?.email).toBe("exact@company.com");
expect(
resolvePermissionedAsEmail("f/specific/script", exactRules)
).toBe("exact@company.com");
expect(
resolvePermissionedAsEmail("f/specific/other", exactRules)
resolvePermissionedAsRule("f/specific/other", exactRules)
).toBeUndefined();
});
@@ -263,11 +262,10 @@ describe("resolvePermissionedAsEmail", () => {
const wildcardRules: PermissionedAsRule[] = [
{ email: "wild@company.com", path_pattern: "f/*/scripts" },
];
const rule = resolvePermissionedAsRule("f/team_a/scripts", wildcardRules);
expect(rule?.email).toBe("wild@company.com");
expect(
resolvePermissionedAsEmail("f/team_a/scripts", wildcardRules)
).toBe("wild@company.com");
expect(
resolvePermissionedAsEmail("f/team_a/nested/scripts", wildcardRules)
resolvePermissionedAsRule("f/team_a/nested/scripts", wildcardRules)
).toBeUndefined();
});
});