mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 08:02:26 +00:00
Add logs for when permissioned as is dealt with
This commit is contained in:
@@ -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)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user