permdock 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +268 -1
- package/dist/a2a/index.d.ts +178 -0
- package/dist/a2a/index.js +271 -0
- package/dist/ai-sdk/index.d.ts +74 -0
- package/dist/ai-sdk/index.js +137 -0
- package/dist/approval-required-TzNB9oQA.js +6 -0
- package/dist/approvals/index.d.ts +3 -0
- package/dist/approvals/index.js +4 -0
- package/dist/approvals-DaxY-Bdg.js +218 -0
- package/dist/approvers-aH0PJkmf.js +20 -0
- package/dist/arazzo-CTeVuXr5.js +70 -0
- package/dist/arazzo-aoTkqYsJ.js +54 -0
- package/dist/authzen/index.d.ts +48 -0
- package/dist/authzen/index.js +336 -0
- package/dist/better-auth/index.d.ts +92 -0
- package/dist/better-auth/index.js +324 -0
- package/dist/budget-JqcRqyhK.d.ts +11 -0
- package/dist/budget-NbEtbJC_.js +11 -0
- package/dist/capability-B68OFnZx.js +171 -0
- package/dist/capability-BDG_nkxt.d.ts +99 -0
- package/dist/catalog/index.d.ts +12 -0
- package/dist/catalog/index.js +2 -0
- package/dist/catalog-B0E6bggc.js +48 -0
- package/dist/catalog-H-vFGwWK.js +55 -0
- package/dist/catalog-doc-BSo_Q_-6.js +298 -0
- package/dist/catalog-idsHc0vo.js +2 -0
- package/dist/catalog-path-CnI0XTFK.js +12 -0
- package/dist/claude-agent/index.d.ts +81 -0
- package/dist/claude-agent/index.js +83 -0
- package/dist/clerk/index.d.ts +56 -0
- package/dist/clerk/index.js +280 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +20 -0
- package/dist/cli/index.d.ts +46 -0
- package/dist/cli/index.js +5 -0
- package/dist/cloud/index.d.ts +91 -0
- package/dist/cloud/index.js +390 -0
- package/dist/cloud-BNQTrmrr.js +50 -0
- package/dist/cloud-Cuxr6Czw.js +117 -0
- package/dist/collect-B7_TIfmg.js +49 -0
- package/dist/collect-O-tOtbnk.js +350 -0
- package/dist/collect-fRPAQIWb.js +2 -0
- package/dist/commands-BMh_Etm2.js +93 -0
- package/dist/compact-y6vfu2_f.js +19 -0
- package/dist/compare-pfyVnBjE.js +12 -0
- package/dist/compile-CQbvyIBr.d.ts +30 -0
- package/dist/compile-D22iwvuV.js +558 -0
- package/dist/config-BPYmjTOp.js +26 -0
- package/dist/connection-JvqwHODC.js +194 -0
- package/dist/context-CKEAabd7.js +73 -0
- package/dist/context-gtAwAILx.js +6 -0
- package/dist/convex/index.d.ts +40 -0
- package/dist/convex/index.js +55 -0
- package/dist/create-BKF3dgFk.d.ts +162 -0
- package/dist/create-CSkhDU9T.js +222 -0
- package/dist/credential-B_rkUNlY.js +326 -0
- package/dist/credentials-BgRX3WEg.js +268 -0
- package/dist/describe-Bt1YD5uS.js +99 -0
- package/dist/describe-DernyItf.d.ts +37 -0
- package/dist/diff-BUzf5Poi.js +48 -0
- package/dist/diff-x5Il8LVa.js +418 -0
- package/dist/digest-B3kc_PgQ.d.ts +19 -0
- package/dist/digest-BI-6aa6d.js +27 -0
- package/dist/doctor-BGle8Gqi.js +1314 -0
- package/dist/doctor-Bqo6FwUa.js +53 -0
- package/dist/drizzle/index.d.ts +72 -0
- package/dist/drizzle/index.js +160 -0
- package/dist/duration-xK2jGFVc.js +25 -0
- package/dist/elysia/index.d.ts +71 -0
- package/dist/elysia/index.js +103 -0
- package/dist/errors-DSMnAVNa.js +43 -0
- package/dist/errors-DbZ1YOnb.d.ts +138 -0
- package/dist/errors-Iy71s7Oj.js +177 -0
- package/dist/errors-m3u9TmDu.js +27 -0
- package/dist/evaluations-DsAU9ppl.js +479 -0
- package/dist/eve/index.d.ts +95 -0
- package/dist/eve/index.js +181 -0
- package/dist/express/index.d.ts +46 -0
- package/dist/express/index.js +108 -0
- package/dist/fastify/index.d.ts +56 -0
- package/dist/fastify/index.js +141 -0
- package/dist/files-B_wAGYmt.js +121 -0
- package/dist/fixtures-Cvaw0gDG.js +64 -0
- package/dist/freeze-y1_jQL4v.js +31 -0
- package/dist/from-snapshot-BMGnhmgv.js +552 -0
- package/dist/generate-CH7ThJ14.js +173 -0
- package/dist/grantee-Bv04_Vzf.js +521 -0
- package/dist/headers-B5RRv3Xm.js +6 -0
- package/dist/headers-C8SHQccD.d.ts +6 -0
- package/dist/helpers-DnSOrhB9.js +682 -0
- package/dist/hono/index.d.ts +70 -0
- package/dist/hono/index.js +131 -0
- package/dist/hosted-BwKQ1BTA.js +265 -0
- package/dist/http-DPuDaChb.d.ts +13 -0
- package/dist/http-GgVApX1k.js +111 -0
- package/dist/index-CltcQ4Bf.d.ts +138 -0
- package/dist/index-LF_hS-yn.d.ts +177 -0
- package/dist/index-fG6NSXlc.d.ts +9 -0
- package/dist/index-xIxJLwOQ.d.ts +415 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +21 -0
- package/dist/jwt/index.d.ts +87 -0
- package/dist/jwt/index.js +571 -0
- package/dist/kernel-YOGCWNLf.js +211 -0
- package/dist/kysely/index.d.ts +77 -0
- package/dist/kysely/index.js +129 -0
- package/dist/load-BZ24CpZP.js +69 -0
- package/dist/load-QOOm1z70.js +2 -0
- package/dist/load-jose-BL6LH4bL.js +11 -0
- package/dist/lru-8q4WZjBV.js +28 -0
- package/dist/manifest-DDQbC6K0.d.ts +118 -0
- package/dist/map-DowSO96j.js +158 -0
- package/dist/map-claims-B_6I1Rn-.js +360 -0
- package/dist/map-error-RJ3ZsUyA.js +16 -0
- package/dist/markers-B0hYLvMD.js +60 -0
- package/dist/may-use-CnFEjVEp.js +37 -0
- package/dist/mcp/index.d.ts +127 -0
- package/dist/mcp/index.js +563 -0
- package/dist/nest/index.d.ts +80 -0
- package/dist/nest/index.js +270 -0
- package/dist/next/client.d.ts +24 -0
- package/dist/next/client.js +31 -0
- package/dist/next/index.d.ts +110 -0
- package/dist/next/index.js +195 -0
- package/dist/next/plugin.d.ts +14 -0
- package/dist/next/plugin.js +2 -0
- package/dist/node/index.d.ts +43 -0
- package/dist/node/index.js +66 -0
- package/dist/openai/index.d.ts +71 -0
- package/dist/openai/index.js +105 -0
- package/dist/openapi/index.d.ts +93 -0
- package/dist/openapi/index.js +3 -0
- package/dist/openapi-CwePaF7C.js +164 -0
- package/dist/openapi-DWD0Uc_4.js +250 -0
- package/dist/openapi-DcqM8nU7.js +306 -0
- package/dist/openapi-import-BWRUNNeW.js +5959 -0
- package/dist/openapi-schema-D8j_T9tO.js +6902 -0
- package/dist/orpc/index.d.ts +64 -0
- package/dist/orpc/index.js +188 -0
- package/dist/otel/index.d.ts +13 -0
- package/dist/otel/index.js +202 -0
- package/dist/package-root-zGkxO3oN.js +30 -0
- package/dist/parse-DPfIvFMU.js +135 -0
- package/dist/pdp/index.d.ts +22 -0
- package/dist/pdp/index.js +795 -0
- package/dist/peer-BOr_kJE1.js +31 -0
- package/dist/permdock-yoZ7EjxX.js +2956 -0
- package/dist/permissions-B4apnahQ.d.ts +185 -0
- package/dist/permissions-B8vCg_bI.d.ts +52 -0
- package/dist/permissions-CofRqesP.js +142 -0
- package/dist/permissions-DC7GROPA.js +600 -0
- package/dist/pg-4GJWDUxG.js +953 -0
- package/dist/pins-CqE1jZuF.js +12 -0
- package/dist/plugin-4gr7IxFJ.js +65 -0
- package/dist/policy-ANp36PnI.js +971 -0
- package/dist/policy-CInmNGkM.d.ts +1959 -0
- package/dist/policy-Cq2fB8Q5.d.ts +20 -0
- package/dist/prisma/index.d.ts +135 -0
- package/dist/prisma/index.js +430 -0
- package/dist/problem-Cgp9-KZA.js +226 -0
- package/dist/prompt-CXYhLfsS.js +32 -0
- package/dist/protected-B4K3BKoX.js +138 -0
- package/dist/protected-BuPAycJT.d.ts +26 -0
- package/dist/protected-wY7nXhY9.d.ts +23 -0
- package/dist/provider-_wx46HpB.js +48 -0
- package/dist/provider-wTAUda2v.d.ts +6 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +6 -0
- package/dist/react/provider-client.d.ts +2 -0
- package/dist/react/provider-client.js +3 -0
- package/dist/react-native/index.d.ts +42 -0
- package/dist/react-native/index.js +174 -0
- package/dist/refs-ChLSxbST.js +90 -0
- package/dist/relations-r4soIFZB.js +426 -0
- package/dist/revocations-Cy06JNx2.d.ts +21 -0
- package/dist/rls-BOFU3chG.js +233 -0
- package/dist/rls-CJVja9Ww.js +2119 -0
- package/dist/rls-graph-DxQ39PJj.js +371 -0
- package/dist/rls-helpers-BxRuif0l.js +1346 -0
- package/dist/rls-import-CXV6LBlS.js +942 -0
- package/dist/rls-migrate-DrFPbf8p.js +366 -0
- package/dist/row-check-BajTgQZ-.d.ts +43 -0
- package/dist/run-CJ6UvS3a.js +155 -0
- package/dist/runtime-DP9ttFh1.js +27 -0
- package/dist/saas-BRrZzJkM.js +1193 -0
- package/dist/schema-P8yoma__.js +296 -0
- package/dist/scim/index.d.ts +2 -0
- package/dist/scim/index.js +2 -0
- package/dist/scim-9NFa0XVZ.js +1445 -0
- package/dist/scopes-cKVp_WTw.js +212 -0
- package/dist/server/index.d.ts +123 -0
- package/dist/server/index.js +8 -0
- package/dist/sha256-BO0swRXL.js +150 -0
- package/dist/sink-Bd9PSyfq.js +141 -0
- package/dist/sink-Cfr9-7YC.d.ts +104 -0
- package/dist/skills-C_lpV4Jx.js +50 -0
- package/dist/skills-Dx3QLnMY.js +110 -0
- package/dist/snapshot-DE8dE_hz.js +1051 -0
- package/dist/solid/index.d.ts +63 -0
- package/dist/solid/index.js +197 -0
- package/dist/source-DoBBYJi2.js +6 -0
- package/dist/sources-DOpW5Ila.d.ts +347 -0
- package/dist/sources-DVHM3Mwq.js +284 -0
- package/dist/sql-Bll1d7y0.js +22 -0
- package/dist/src-CTNT-PBW.js +430 -0
- package/dist/ssf/index.d.ts +3 -0
- package/dist/ssf/index.js +622 -0
- package/dist/store-Bq90395i.d.ts +27 -0
- package/dist/store-C6hFW6ta.js +508 -0
- package/dist/stores-CkG2HrSg.js +140 -0
- package/dist/stores-CvOnu_2T.d.ts +41 -0
- package/dist/stream-BhMfZOwr.d.ts +12 -0
- package/dist/stream-Q_xlB6Ko.js +52 -0
- package/dist/style-CTm1AXDT.js +22 -0
- package/dist/subject-C6dLxqHU.js +24 -0
- package/dist/subject-settings-xt7V845g.js +82 -0
- package/dist/supabase/index.d.ts +67 -0
- package/dist/supabase/index.js +4 -0
- package/dist/supabase/middleware.d.ts +77 -0
- package/dist/supabase/middleware.js +68 -0
- package/dist/supabase-D8UlBA7Z.js +83 -0
- package/dist/supabase-DTxGtYdi.js +796 -0
- package/dist/supabase-hook-DW-xP4hU.js +2 -0
- package/dist/supabase-hook-D_OqGb_V.js +1528 -0
- package/dist/svelte/Protected.svelte +34 -0
- package/dist/svelte/index.d.ts +10 -0
- package/dist/svelte/index.js +53 -0
- package/dist/svelte/runtime.d.ts +6 -0
- package/dist/svelte/runtime.js +3 -0
- package/dist/svelte/source.d.ts +10 -0
- package/dist/svelte/source.js +8 -0
- package/dist/terminal/index.d.ts +171 -0
- package/dist/terminal/index.js +821 -0
- package/dist/testing/index.d.ts +656 -0
- package/dist/testing/index.js +3208 -0
- package/dist/testing/saas/index.d.ts +194 -0
- package/dist/testing/saas/index.js +3 -0
- package/dist/testing/saas/permissions.d.ts +2 -0
- package/dist/testing/saas/permissions.js +2 -0
- package/dist/text-diff-BdkkYSbi.js +15 -0
- package/dist/thenable-okc2xDsj.js +10 -0
- package/dist/timeout-D51niuQP.js +8 -0
- package/dist/trpc/index.d.ts +80 -0
- package/dist/trpc/index.js +205 -0
- package/dist/types-B8w5PFtS.d.ts +141 -0
- package/dist/types-B9dmiiab.d.ts +66 -0
- package/dist/types-BBK-exvJ.d.ts +177 -0
- package/dist/types-BvBk1wgn.d.ts +118 -0
- package/dist/types-CxquUByI.d.ts +9 -0
- package/dist/types-D6Z6-rKJ.d.ts +312 -0
- package/dist/types-DbWdEfcm.d.ts +87 -0
- package/dist/types-bva3i4Gf.d.ts +98 -0
- package/dist/types-i4BAOvdb.d.ts +97 -0
- package/dist/unplugin/index.d.ts +7 -0
- package/dist/unplugin/index.js +30 -0
- package/dist/usage-1ozMubLY.js +42 -0
- package/dist/usage-CUnK6Ou9.js +530 -0
- package/dist/usage-PyEor6GA.js +2 -0
- package/dist/validity-D0uRmJKJ.js +224 -0
- package/dist/verifier-B90agnhl.js +461 -0
- package/dist/version-CnSh33Fj.js +16 -0
- package/dist/vocabulary-Dk0fs-Jk.d.ts +56 -0
- package/dist/vue/index.d.ts +57 -0
- package/dist/vue/index.js +203 -0
- package/dist/webmcp/index.d.ts +75 -0
- package/dist/webmcp/index.js +313 -0
- package/package.json +423 -4
- package/rulesets/permdock.yaml +29 -0
- package/schemas/catalog-v1.json +507 -0
- package/schemas/openapi/oas-3.1.json +1298 -0
- package/schemas/openapi/oas-3.2.json +1516 -0
- package/schemas/openapi/overlay-1.1.json +79 -0
- package/schemas/supabase-claims-v1.json +139 -0
- package/schemas/supabase-manifest-v1.json +217 -0
- package/skills/permdock/SKILL.md +66 -0
- package/skills/permdock-agents/SKILL.md +77 -0
- package/skills/permdock-agents/references/runtimes.md +211 -0
- package/skills/permdock-approvals/SKILL.md +80 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +83 -0
- package/skills/permdock-audit/SKILL.md +67 -0
- package/skills/permdock-credentials/SKILL.md +55 -0
- package/skills/permdock-credentials/references/tokens-keys-links.md +127 -0
- package/skills/permdock-data/SKILL.md +57 -0
- package/skills/permdock-data/references/queries-and-rls.md +54 -0
- package/skills/permdock-data/references/relationships.md +65 -0
- package/skills/permdock-tenancy/SKILL.md +77 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +83 -0
- package/skills/permdock-wire/SKILL.md +116 -0
- package/skills/permdock-wire/references/adapters.md +480 -0
- package/skills/permdock-wire/references/policy-options.md +43 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { d as isFieldRelation, h as isSelfParented, p as isPrincipalRelation, r as expandRelation, u as isEdgeRelation } from "./permissions-DC7GROPA.js";
|
|
2
|
+
import { u as scopeList } from "./scopes-cKVp_WTw.js";
|
|
3
|
+
import { f as relationCondition, s as flattenGrantee } from "./grantee-Bv04_Vzf.js";
|
|
4
|
+
import { c as relationArmSql } from "./compile-D22iwvuV.js";
|
|
5
|
+
import "./sources-DVHM3Mwq.js";
|
|
6
|
+
import { F as qualifiedTable, I as quoteIdent, L as quoteLiteral, O as linkHelper, R as quoteTable, T as graphSqlText, g as activeUserSql, m as CLOSURE, t as HELPERS, w as graphHelper } from "./rls-helpers-BxRuif0l.js";
|
|
7
|
+
//#region src/cli/helper-calls.ts
|
|
8
|
+
/**
|
|
9
|
+
* Tables whose policies other packages write (better-supabase `defineBucket`
|
|
10
|
+
* and `defineTopic` in `permdock` mode) by calling the SQL helpers directly.
|
|
11
|
+
*/
|
|
12
|
+
const HELPER_TABLES = ["storage.objects", "realtime.messages"];
|
|
13
|
+
const HELPER_CALL = new RegExp(String.raw`\b(?:${HELPERS.has}|permitted_[a-z][a-z0-9_]*_ids)\s*\(\s*'((?:[^']|'')+)'`, "giu");
|
|
14
|
+
const ANY_HELPER = new RegExp(String.raw`\b(?:${HELPERS.has}|permitted_[a-z][a-z0-9_]*_ids|member_[a-z][a-z0-9_]*_ids)\s*\(`, "iu");
|
|
15
|
+
/** Whether a policy expression calls any generated helper, the key-less `member_<scope>_ids` included. */
|
|
16
|
+
function callsHelper(sql) {
|
|
17
|
+
return ANY_HELPER.test(sql);
|
|
18
|
+
}
|
|
19
|
+
/** The permission keys a policy expression passes to `permdock_has` or `permitted_<scope>_ids`, `#n` stripped. */
|
|
20
|
+
function helperCallKeys(sql) {
|
|
21
|
+
const keys = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const [, literal = ""] of sql.matchAll(HELPER_CALL)) {
|
|
23
|
+
const key = literal.replaceAll("''", "'").split("#")[0] ?? "";
|
|
24
|
+
if (key !== "") keys.add(key);
|
|
25
|
+
}
|
|
26
|
+
return [...keys];
|
|
27
|
+
}
|
|
28
|
+
const CREATE_POLICY = /\bcreate\s+policy\s+("(?:[^"]|"")+"|\w+)\s+on\s+((?:"[^"]+"|\w+)\.(?:"[^"]+"|\w+))([\s\S]*?);/giu;
|
|
29
|
+
function tableName(name) {
|
|
30
|
+
return name.replaceAll("\"", "").toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
function helperTable(name) {
|
|
33
|
+
const table = tableName(name);
|
|
34
|
+
return HELPER_TABLES.find((candidate) => candidate === table);
|
|
35
|
+
}
|
|
36
|
+
/** `create policy` statements on {@link HELPER_TABLES} in migration SQL, with the keys their helper calls pass. */
|
|
37
|
+
function helperTablePolicies(sql) {
|
|
38
|
+
const text = sql.replaceAll(/--[^\n]*/gu, "").replaceAll(/\/\*[\s\S]*?\*\//gu, "");
|
|
39
|
+
const found = [];
|
|
40
|
+
for (const [, name = "", target = "", body = ""] of text.matchAll(CREATE_POLICY)) {
|
|
41
|
+
const table = helperTable(target);
|
|
42
|
+
const keys = helperCallKeys(body);
|
|
43
|
+
if (table !== void 0 && keys.length > 0) found.push({
|
|
44
|
+
table,
|
|
45
|
+
name: name.replaceAll("\"", ""),
|
|
46
|
+
keys
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return found;
|
|
50
|
+
}
|
|
51
|
+
/** `pg_policies` rows on {@link HELPER_TABLES}, read by `rls verify --db`. */
|
|
52
|
+
const HELPER_TABLE_POLICIES_SQL = `select schemaname || '.' || tablename as target, policyname, coalesce(qual, '') || ' ' || coalesce(with_check, '') as body
|
|
53
|
+
from pg_policies
|
|
54
|
+
where (schemaname, tablename) in (('storage', 'objects'), ('realtime', 'messages'))`;
|
|
55
|
+
/** The same rows as {@link helperTablePolicies}, from `pg_policies`. */
|
|
56
|
+
function helperTablePoliciesFromRows(rows) {
|
|
57
|
+
const found = [];
|
|
58
|
+
for (const row of rows) {
|
|
59
|
+
const table = helperTable(String(row["target"] ?? ""));
|
|
60
|
+
const keys = helperCallKeys(String(row["body"] ?? ""));
|
|
61
|
+
if (table !== void 0 && keys.length > 0) found.push({
|
|
62
|
+
table,
|
|
63
|
+
name: String(row["policyname"] ?? ""),
|
|
64
|
+
keys
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return found;
|
|
68
|
+
}
|
|
69
|
+
/** The message PD037 and `rls verify --db` report for one policy. */
|
|
70
|
+
function rowConditionMessage(policy, keys) {
|
|
71
|
+
return `${policy.table} policy '${policy.name}' calls the SQL helpers with ${keys.join(", ")}, whose grants carry row conditions the helpers do not check: the policy grants more than the application does`;
|
|
72
|
+
}
|
|
73
|
+
const ROW_CONDITION_FIX = "use permissions whose catalog entry has rowConditions: false for storage and realtime policies, or write the condition into the policy yourself";
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/cli/rls-graph.ts
|
|
76
|
+
function qualified(ctx, name) {
|
|
77
|
+
return `${quoteIdent(ctx.schema ?? "permdock")}.${name}`;
|
|
78
|
+
}
|
|
79
|
+
function tableFor(resource, tables) {
|
|
80
|
+
return tables?.[resource] ?? resource;
|
|
81
|
+
}
|
|
82
|
+
/** Every `related` condition the policy's grants compile to, with the resource graph they need, groups included. */
|
|
83
|
+
function graphPlan(policy) {
|
|
84
|
+
const scopes = scopeList(policy.scopes);
|
|
85
|
+
const plan = /* @__PURE__ */ new Map();
|
|
86
|
+
const entryFor = (node) => {
|
|
87
|
+
const entry = plan.get(node.name) ?? {
|
|
88
|
+
node,
|
|
89
|
+
relations: /* @__PURE__ */ new Set(),
|
|
90
|
+
links: /* @__PURE__ */ new Set()
|
|
91
|
+
};
|
|
92
|
+
plan.set(node.name, entry);
|
|
93
|
+
return entry;
|
|
94
|
+
};
|
|
95
|
+
const addRelation = (node, relation) => {
|
|
96
|
+
const entry = entryFor(node);
|
|
97
|
+
if (entry.relations.has(relation)) return;
|
|
98
|
+
entry.relations.add(relation);
|
|
99
|
+
for (const name of expandRelation(node, relation)) {
|
|
100
|
+
const spec = node.relations[name];
|
|
101
|
+
if (!isEdgeRelation(spec) || spec.groups === void 0) continue;
|
|
102
|
+
for (const [resource, groupRelation] of Object.entries(spec.groups.resources)) {
|
|
103
|
+
const target = policy.resources.get(resource);
|
|
104
|
+
if (target !== void 0 && target.name !== node.name) addRelation(target, groupRelation);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
for (const grant of policy.grants) {
|
|
109
|
+
const row = policy.resources.get(grant.permission.resource);
|
|
110
|
+
for (const item of flattenGrantee(grant.to)) {
|
|
111
|
+
if (item.kind !== "relation") continue;
|
|
112
|
+
const condition = relationCondition(item, row, scopes, { resources: policy.resources });
|
|
113
|
+
if (condition?.op !== "related") continue;
|
|
114
|
+
const node = policy.resources.get(condition.resource);
|
|
115
|
+
if (node === void 0) continue;
|
|
116
|
+
addRelation(node, condition.relation);
|
|
117
|
+
const entry = entryFor(node);
|
|
118
|
+
if (condition.depth > 0 && isSelfParented(node)) entry.closure = Math.max(entry.closure ?? 0, condition.depth);
|
|
119
|
+
const hops = condition.hops ?? [];
|
|
120
|
+
for (let index = 1; index < hops.length; index += 1) {
|
|
121
|
+
const from = policy.resources.get(hops[index - 1]?.resource ?? "");
|
|
122
|
+
const hop = hops[index];
|
|
123
|
+
if (from !== void 0 && hop !== void 0) entryFor(from).links.add(hop.link);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return plan;
|
|
128
|
+
}
|
|
129
|
+
/** The closure depth per walked resource, as `RlsSqlContext.graph` carries it. */
|
|
130
|
+
function closureDepths(plan) {
|
|
131
|
+
const out = {};
|
|
132
|
+
for (const [name, entry] of plan) if (entry.closure !== void 0) out[name] = entry.closure;
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
function tableSql(name) {
|
|
136
|
+
return quoteTable(qualifiedTable(name));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The `permitted_<resource>_ids(relation)` arm for one concrete relation:
|
|
140
|
+
* ids of `resource` the subject holds it on, directly or through a group.
|
|
141
|
+
* `asked` are the relation names that include it, so a call for any of them
|
|
142
|
+
* (or for all, with null) reads this arm.
|
|
143
|
+
*/
|
|
144
|
+
function relationArm(ctx, plan, node, relation, asked, tables) {
|
|
145
|
+
const spec = node.relations[relation];
|
|
146
|
+
if (!isEdgeRelation(spec) && !isPrincipalRelation(spec) && (!isFieldRelation(spec) || spec.memberOf !== void 0)) throw new Error(`PermDock CLI: relation '${relation}' on ${node.name} is not a graph relation RLS can compile`);
|
|
147
|
+
const guard = asked.length === 1 ? `(p_relation is null or p_relation = ${quoteLiteral(asked[0] ?? relation)})` : `(p_relation is null or p_relation in (${asked.map(quoteLiteral).join(", ")}))`;
|
|
148
|
+
return ` select a.id from (${graphSqlText(relationArmSql(node.name, relation, {
|
|
149
|
+
resources: new Map([...plan].map(([name, item]) => [name, item.node])),
|
|
150
|
+
...tables === void 0 ? {} : { tables },
|
|
151
|
+
qualify: qualifiedTable,
|
|
152
|
+
holders: (resource, groupRelation) => [{ text: `select ${qualified(ctx, graphHelper(resource))}(${quoteLiteral(groupRelation)})` }]
|
|
153
|
+
}), ctx)}) a\n where ${guard}${activeUserSql(ctx).map((part) => ` and ${part}`).join("")}`;
|
|
154
|
+
}
|
|
155
|
+
function permittedSql(ctx, plan, entry, tables) {
|
|
156
|
+
const fn = qualified(ctx, graphHelper(entry.node.name));
|
|
157
|
+
const asked = [...entry.relations].toSorted();
|
|
158
|
+
const arms = [...new Set(asked.flatMap((name) => expandRelation(entry.node, name)))].toSorted().map((relation) => relationArm(ctx, plan, entry.node, relation, asked.filter((name) => expandRelation(entry.node, name).includes(relation)), tables));
|
|
159
|
+
if (arms.length === 0) arms.push(" select null::text where false");
|
|
160
|
+
return `-- ${entry.node.name}: ids the subject holds a relation on (all relations when p_relation is null)
|
|
161
|
+
create or replace function ${fn}(p_relation text)
|
|
162
|
+
returns setof text
|
|
163
|
+
language sql
|
|
164
|
+
stable
|
|
165
|
+
security definer
|
|
166
|
+
set search_path = ''
|
|
167
|
+
as $$
|
|
168
|
+
${arms.join("\n union\n")}
|
|
169
|
+
$$;
|
|
170
|
+
revoke execute on function ${fn}(text) from public, anon;
|
|
171
|
+
grant execute on function ${fn}(text) to authenticated;`;
|
|
172
|
+
}
|
|
173
|
+
/** `permdock_link_<resource>_<link>(ids)`: ids of `resource` whose link points into `ids`, read past the table's own policies. */
|
|
174
|
+
function linkSql(ctx, node, link, tables) {
|
|
175
|
+
const spec = Object.hasOwn(node.links, link) ? node.links[link] : void 0;
|
|
176
|
+
if (spec === void 0) throw new Error(`PermDock CLI: ${node.name} has no link '${link}'`);
|
|
177
|
+
const fn = qualified(ctx, linkHelper(node.name, link));
|
|
178
|
+
return `-- ${node.name}.${link}: rows whose ${spec.field} points at one of the given ${spec.resource} ids
|
|
179
|
+
create or replace function ${fn}(p_ids text[])
|
|
180
|
+
returns setof text
|
|
181
|
+
language sql
|
|
182
|
+
stable
|
|
183
|
+
security definer
|
|
184
|
+
set search_path = ''
|
|
185
|
+
as $$
|
|
186
|
+
select t.${quoteIdent(node.id)}::text from ${tableSql(tableFor(node.name, tables))} t
|
|
187
|
+
where t.${quoteIdent(spec.field)}::text = any(p_ids)
|
|
188
|
+
$$;
|
|
189
|
+
revoke execute on function ${fn}(text[]) from public, anon;
|
|
190
|
+
grant execute on function ${fn}(text[]) to authenticated;`;
|
|
191
|
+
}
|
|
192
|
+
function closureTableSql(ctx) {
|
|
193
|
+
const table = qualified(ctx, CLOSURE.table);
|
|
194
|
+
return `-- the object graph: one row per (ancestor, descendant) within each walked resource's depth,
|
|
195
|
+
-- never crossing a restricted row; kept current by statement triggers on each self-parented table
|
|
196
|
+
create table if not exists ${table} (
|
|
197
|
+
resource text not null,
|
|
198
|
+
ancestor text not null,
|
|
199
|
+
descendant text not null,
|
|
200
|
+
depth integer not null check (depth >= 0),
|
|
201
|
+
primary key (resource, descendant, ancestor)
|
|
202
|
+
);
|
|
203
|
+
create index if not exists ${quoteIdent(`${CLOSURE.table}_ancestor`)} on ${table} (resource, ancestor);
|
|
204
|
+
alter table ${table} enable row level security;
|
|
205
|
+
revoke all on table ${table} from anon, authenticated, public;
|
|
206
|
+
grant select on table ${table} to authenticated;`;
|
|
207
|
+
}
|
|
208
|
+
function closureObjectsSql(ctx, entry, depth, tables) {
|
|
209
|
+
const { node } = entry;
|
|
210
|
+
const parent = node.parent;
|
|
211
|
+
if (parent === void 0) return "";
|
|
212
|
+
const name = node.name;
|
|
213
|
+
const closure = qualified(ctx, CLOSURE.table);
|
|
214
|
+
const table = tableSql(tableFor(name, tables));
|
|
215
|
+
const id = quoteIdent(node.id);
|
|
216
|
+
const up = quoteIdent(parent.field);
|
|
217
|
+
const stop = node.restricted === void 0 ? "false" : `coalesce(t.${quoteIdent(node.restricted)}, false)`;
|
|
218
|
+
const stopParent = node.restricted === void 0 ? "false" : `coalesce(p.${quoteIdent(node.restricted)}, false)`;
|
|
219
|
+
const refresh = qualified(ctx, `${CLOSURE.table}_${name}`);
|
|
220
|
+
const changed = qualified(ctx, `${CLOSURE.table}_${name}_changed`);
|
|
221
|
+
const literal = quoteLiteral(name);
|
|
222
|
+
const restrictedChanged = node.restricted === void 0 ? "" : ` or n.${quoteIdent(node.restricted)} is distinct from o.${quoteIdent(node.restricted)}`;
|
|
223
|
+
const trigger = (suffix) => quoteIdent(`${CLOSURE.table}_${name}_${suffix}`);
|
|
224
|
+
return `-- ${name}: rows reach their ancestors up to depth ${String(depth)}, stopping at a restricted row
|
|
225
|
+
drop policy if exists ${quoteIdent(`${CLOSURE.table}_${name}`)} on ${closure};
|
|
226
|
+
create policy ${quoteIdent(`${CLOSURE.table}_${name}`)} on ${closure}
|
|
227
|
+
for select to authenticated
|
|
228
|
+
using (resource = ${literal} and ancestor in (select ${qualified(ctx, graphHelper(name))}(null)));
|
|
229
|
+
create or replace function ${refresh}(p_ids text[])
|
|
230
|
+
returns void
|
|
231
|
+
language plpgsql
|
|
232
|
+
security definer
|
|
233
|
+
set search_path = ''
|
|
234
|
+
as $$
|
|
235
|
+
declare
|
|
236
|
+
v_cycle text;
|
|
237
|
+
v_affected text[];
|
|
238
|
+
begin
|
|
239
|
+
with recursive walk(start, next, path) as (
|
|
240
|
+
select t.${id}, t.${up}, array[t.${id}::text]
|
|
241
|
+
from ${table} t
|
|
242
|
+
where t.${id}::text = any(p_ids)
|
|
243
|
+
union all
|
|
244
|
+
select walk.start, t.${up}, walk.path || t.${id}::text
|
|
245
|
+
from walk join ${table} t on t.${id} = walk.next
|
|
246
|
+
where not (walk.next::text = any(walk.path))
|
|
247
|
+
)
|
|
248
|
+
select walk.start::text into v_cycle
|
|
249
|
+
from walk
|
|
250
|
+
where walk.next::text = any(walk.path)
|
|
251
|
+
limit 1;
|
|
252
|
+
if v_cycle is not null then
|
|
253
|
+
raise exception 'permdock: % % is its own ancestor', ${literal}, v_cycle
|
|
254
|
+
using errcode = 'check_violation';
|
|
255
|
+
end if;
|
|
256
|
+
with recursive down(id, level) as (
|
|
257
|
+
select t.${id}, 0
|
|
258
|
+
from ${table} t
|
|
259
|
+
where t.${id}::text = any(p_ids) or t.${up}::text = any(p_ids)
|
|
260
|
+
union
|
|
261
|
+
select t.${id}, down.level + 1
|
|
262
|
+
from down join ${table} t on t.${up} = down.id
|
|
263
|
+
where down.level < ${String(depth)}
|
|
264
|
+
)
|
|
265
|
+
select coalesce(array_agg(distinct down.id::text), '{}') into v_affected from down;
|
|
266
|
+
delete from ${closure} c
|
|
267
|
+
where c.resource = ${literal}
|
|
268
|
+
and (c.descendant = any(p_ids) or c.descendant = any(v_affected));
|
|
269
|
+
insert into ${closure} (resource, ancestor, descendant, depth)
|
|
270
|
+
with recursive walk(descendant, ancestor, parent, depth, stop) as (
|
|
271
|
+
select t.${id}::text, t.${id}, t.${up}, 0, ${stop}
|
|
272
|
+
from ${table} t
|
|
273
|
+
where t.${id}::text = any(v_affected)
|
|
274
|
+
union all
|
|
275
|
+
select walk.descendant, p.${id}, p.${up}, walk.depth + 1, ${stopParent}
|
|
276
|
+
from walk join ${table} p on p.${id} = walk.parent
|
|
277
|
+
where not walk.stop and walk.depth < ${String(depth)}
|
|
278
|
+
)
|
|
279
|
+
select ${literal}, walk.ancestor::text, walk.descendant, walk.depth from walk
|
|
280
|
+
on conflict do nothing;
|
|
281
|
+
end
|
|
282
|
+
$$;
|
|
283
|
+
revoke execute on function ${refresh}(text[]) from public, anon, authenticated;
|
|
284
|
+
create or replace function ${changed}()
|
|
285
|
+
returns trigger
|
|
286
|
+
language plpgsql
|
|
287
|
+
security definer
|
|
288
|
+
set search_path = ''
|
|
289
|
+
as $$
|
|
290
|
+
begin
|
|
291
|
+
if tg_op = 'TRUNCATE' then
|
|
292
|
+
delete from ${closure} where resource = ${literal};
|
|
293
|
+
elsif tg_op = 'INSERT' then
|
|
294
|
+
perform ${refresh}(array(select n.${id}::text from new_rows n));
|
|
295
|
+
elsif tg_op = 'DELETE' then
|
|
296
|
+
perform ${refresh}(array(select o.${id}::text from old_rows o));
|
|
297
|
+
else
|
|
298
|
+
perform ${refresh}(array(
|
|
299
|
+
select n.${id}::text
|
|
300
|
+
from new_rows n left join old_rows o on o.${id} = n.${id}
|
|
301
|
+
where o.${id} is null or n.${up} is distinct from o.${up}${restrictedChanged}
|
|
302
|
+
union
|
|
303
|
+
select o.${id}::text
|
|
304
|
+
from old_rows o left join new_rows n on n.${id} = o.${id}
|
|
305
|
+
where n.${id} is null
|
|
306
|
+
));
|
|
307
|
+
end if;
|
|
308
|
+
return null;
|
|
309
|
+
end
|
|
310
|
+
$$;
|
|
311
|
+
revoke execute on function ${changed}() from public, anon, authenticated;
|
|
312
|
+
drop trigger if exists ${trigger("insert")} on ${table};
|
|
313
|
+
create trigger ${trigger("insert")} after insert on ${table}
|
|
314
|
+
referencing new table as new_rows
|
|
315
|
+
for each statement execute function ${changed}();
|
|
316
|
+
drop trigger if exists ${trigger("update")} on ${table};
|
|
317
|
+
create trigger ${trigger("update")} after update on ${table}
|
|
318
|
+
referencing old table as old_rows new table as new_rows
|
|
319
|
+
for each statement execute function ${changed}();
|
|
320
|
+
drop trigger if exists ${trigger("delete")} on ${table};
|
|
321
|
+
create trigger ${trigger("delete")} after delete on ${table}
|
|
322
|
+
referencing old table as old_rows
|
|
323
|
+
for each statement execute function ${changed}();
|
|
324
|
+
drop trigger if exists ${trigger("truncate")} on ${table};
|
|
325
|
+
create trigger ${trigger("truncate")} after truncate on ${table}
|
|
326
|
+
for each statement execute function ${changed}();
|
|
327
|
+
select ${refresh}(array(select t.${id}::text from ${table} t));`;
|
|
328
|
+
}
|
|
329
|
+
/** Group resources a resource's asked relations name, other than itself. */
|
|
330
|
+
function groupTargets(entry) {
|
|
331
|
+
const out = /* @__PURE__ */ new Set();
|
|
332
|
+
for (const asked of entry.relations) for (const name of expandRelation(entry.node, asked)) {
|
|
333
|
+
const spec = entry.node.relations[name];
|
|
334
|
+
const resources = isEdgeRelation(spec) ? Object.keys(spec.groups?.resources ?? {}) : [];
|
|
335
|
+
for (const resource of resources) if (resource !== entry.node.name) out.add(resource);
|
|
336
|
+
}
|
|
337
|
+
return [...out].toSorted();
|
|
338
|
+
}
|
|
339
|
+
/** Plan entries by name, each after the group resources its helper calls (SQL function bodies are checked on create). */
|
|
340
|
+
function dependencyOrder(plan) {
|
|
341
|
+
const out = [];
|
|
342
|
+
const done = /* @__PURE__ */ new Set();
|
|
343
|
+
const visit = (name) => {
|
|
344
|
+
const entry = plan.get(name);
|
|
345
|
+
if (entry === void 0 || done.has(name)) return;
|
|
346
|
+
done.add(name);
|
|
347
|
+
for (const target of groupTargets(entry)) visit(target);
|
|
348
|
+
out.push(entry);
|
|
349
|
+
};
|
|
350
|
+
for (const name of [...plan.keys()].toSorted()) visit(name);
|
|
351
|
+
return out;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* The graph preamble: `permitted_<resource>_ids` for every resource a graph
|
|
355
|
+
* grant reads, and the closure table with its triggers for every
|
|
356
|
+
* self-parented resource a grant walks. Empty when no grant is a graph grant.
|
|
357
|
+
*/
|
|
358
|
+
function graphSql(ctx, plan, tables) {
|
|
359
|
+
if (plan.size === 0) return "";
|
|
360
|
+
for (const name of plan.keys()) if (ctx.scopes.some((scope) => scope.name === name)) throw new Error(`PermDock CLI: resource '${name}' has graph relations and shares its name with the ${name} scope, so ${graphHelper(name)} would replace the scope helper; rename the resource (permdock doctor PD032)`);
|
|
361
|
+
const entries = dependencyOrder(plan);
|
|
362
|
+
const chunks = entries.flatMap((entry) => [...entry.relations.size === 0 ? [] : [permittedSql(ctx, plan, entry, tables)], ...[...entry.links].toSorted().map((link) => linkSql(ctx, entry.node, link, tables))]);
|
|
363
|
+
const walked = entries.filter((entry) => entry.closure !== void 0);
|
|
364
|
+
if (walked.length > 0) {
|
|
365
|
+
chunks.push(closureTableSql(ctx));
|
|
366
|
+
for (const entry of walked) chunks.push(closureObjectsSql(ctx, entry, entry.closure ?? 0, tables));
|
|
367
|
+
}
|
|
368
|
+
return `${chunks.join("\n\n")}\n`;
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
371
|
+
export { HELPER_TABLE_POLICIES_SQL as a, helperCallKeys as c, rowConditionMessage as d, HELPER_TABLES as i, helperTablePolicies as l, graphPlan as n, ROW_CONDITION_FIX as o, graphSql as r, callsHelper as s, closureDepths as t, helperTablePoliciesFromRows as u };
|