121 lines
6.4 KiB
JavaScript
121 lines
6.4 KiB
JavaScript
// Standalone JS port of `lib/visibility.ts` for offline verification.
|
|
// Mirrors the synchronous decision logic — DB call (assignments) is
|
|
// faked as an array param.
|
|
|
|
function scopeFor(user) {
|
|
if (user.isPlatform) return "all";
|
|
if (user.roles.includes("owner")) return "org";
|
|
return "assigned";
|
|
}
|
|
|
|
function listVisibleTenants(user, all, assignments = []) {
|
|
const scope = scopeFor(user);
|
|
if (scope === "all") return all;
|
|
|
|
const orgScoped = all.filter(
|
|
(t) => t.metadata.labels?.["pieced.ch/zitadel-org-id"] === user.orgId
|
|
);
|
|
if (scope === "org") return orgScoped;
|
|
|
|
const allowed = new Set(assignments);
|
|
return orgScoped.filter((t) => allowed.has(t.metadata.name));
|
|
}
|
|
|
|
function canUserSeeTenant(user, tenant, assignments = []) {
|
|
const scope = scopeFor(user);
|
|
if (scope === "all") return true;
|
|
if (tenant.metadata.labels?.["pieced.ch/zitadel-org-id"] !== user.orgId) {
|
|
return false;
|
|
}
|
|
if (scope === "org") return true;
|
|
return assignments.includes(tenant.metadata.name);
|
|
}
|
|
|
|
function canSeeInflightRequests(user) {
|
|
return scopeFor(user) !== "assigned";
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test fixtures
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const platformAdmin = { isPlatform: true, roles: ["platform_admin"], orgId: "platform-org", id: "u-admin" };
|
|
const owner = { isPlatform: false, roles: ["owner"], orgId: "org-acme", id: "u-owner" };
|
|
const userOnly = { isPlatform: false, roles: ["user"], orgId: "org-acme", id: "u-alice" };
|
|
const noRoles = { isPlatform: false, roles: [], orgId: "org-acme", id: "u-bob" };
|
|
|
|
const tenantA = { metadata: { name: "acme-prod-12345678", labels: { "pieced.ch/zitadel-org-id": "org-acme" } } };
|
|
const tenantB = { metadata: { name: "acme-dev-87654321", labels: { "pieced.ch/zitadel-org-id": "org-acme" } } };
|
|
const tenantC = { metadata: { name: "other-corp-aaaa", labels: { "pieced.ch/zitadel-org-id": "org-other" } } };
|
|
|
|
const allTenants = [tenantA, tenantB, tenantC];
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// listVisibleTenants
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const listCases = [
|
|
{ user: platformAdmin, assignments: [], expected: ["acme-prod-12345678", "acme-dev-87654321", "other-corp-aaaa"], note: "platform sees all" },
|
|
{ user: owner, assignments: [], expected: ["acme-prod-12345678", "acme-dev-87654321"], note: "owner sees all org tenants" },
|
|
{ user: owner, assignments: ["acme-prod-12345678"], expected: ["acme-prod-12345678", "acme-dev-87654321"], note: "owner ignores assignment table even if rows exist" },
|
|
{ user: userOnly, assignments: [], expected: [], note: "user with no assignments sees nothing" },
|
|
{ user: userOnly, assignments: ["acme-prod-12345678"], expected: ["acme-prod-12345678"], note: "user sees only assigned tenants" },
|
|
{ user: userOnly, assignments: ["acme-prod-12345678", "acme-dev-87654321"], expected: ["acme-prod-12345678", "acme-dev-87654321"], note: "user sees multiple assigned tenants" },
|
|
{ user: userOnly, assignments: ["other-corp-aaaa"], expected: [], note: "stale assignment to other-org tenant doesn't leak" },
|
|
{ user: noRoles, assignments: [], expected: [], note: "no roles is treated as user-scope (empty)" },
|
|
];
|
|
|
|
let pass = 0, fail = 0;
|
|
|
|
console.log("--- listVisibleTenants ---");
|
|
for (const c of listCases) {
|
|
const got = listVisibleTenants(c.user, allTenants, c.assignments).map((t) => t.metadata.name);
|
|
const ok = JSON.stringify(got) === JSON.stringify(c.expected);
|
|
console.log(`${ok ? "PASS" : "FAIL"} got=${JSON.stringify(got)} want=${JSON.stringify(c.expected)} [${c.note}]`);
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// canUserSeeTenant
|
|
// ---------------------------------------------------------------------------
|
|
|
|
console.log("\n--- canUserSeeTenant ---");
|
|
const seeCases = [
|
|
{ user: platformAdmin, tenant: tenantA, assignments: [], expected: true, note: "platform sees same-cluster tenant" },
|
|
{ user: platformAdmin, tenant: tenantC, assignments: [], expected: true, note: "platform sees other-org tenant" },
|
|
{ user: owner, tenant: tenantA, assignments: [], expected: true, note: "owner sees own-org tenant" },
|
|
{ user: owner, tenant: tenantC, assignments: [], expected: false, note: "owner does NOT see other-org tenant" },
|
|
{ user: userOnly, tenant: tenantA, assignments: ["acme-prod-12345678"], expected: true, note: "user sees assigned tenant" },
|
|
{ user: userOnly, tenant: tenantA, assignments: [], expected: false, note: "user does NOT see un-assigned own-org tenant" },
|
|
{ user: userOnly, tenant: tenantC, assignments: ["other-corp-aaaa"], expected: false, note: "user does NOT see other-org tenant even with stale assignment" },
|
|
];
|
|
|
|
for (const c of seeCases) {
|
|
const got = canUserSeeTenant(c.user, c.tenant, c.assignments);
|
|
const ok = got === c.expected;
|
|
console.log(`${ok ? "PASS" : "FAIL"} got=${got} want=${c.expected} [${c.note}]`);
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// canSeeInflightRequests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
console.log("\n--- canSeeInflightRequests ---");
|
|
const requestCases = [
|
|
{ user: platformAdmin, expected: true, note: "platform sees in-flight" },
|
|
{ user: owner, expected: true, note: "owner sees in-flight" },
|
|
{ user: userOnly, expected: false, note: "user-role does NOT see in-flight" },
|
|
{ user: noRoles, expected: false, note: "no-roles does NOT see in-flight" },
|
|
];
|
|
|
|
for (const c of requestCases) {
|
|
const got = canSeeInflightRequests(c.user);
|
|
const ok = got === c.expected;
|
|
console.log(`${ok ? "PASS" : "FAIL"} got=${got} want=${c.expected} [${c.note}]`);
|
|
if (ok) pass++; else fail++;
|
|
}
|
|
|
|
console.log(`\n${pass} pass, ${fail} fail`);
|
|
process.exit(fail === 0 ? 0 : 1);
|