permdock 0.1.0 → 0.2.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 +106 -47
- package/dist/a2a/index.d.ts +7 -16
- package/dist/a2a/index.js +59 -98
- package/dist/adapter-context-mlWsVGGg.js +23 -0
- package/dist/ai-sdk/index.d.ts +37 -16
- package/dist/ai-sdk/index.js +30 -13
- package/dist/approvals/index.d.ts +3 -3
- package/dist/approvals/index.js +5 -4
- package/dist/{approvals-DaxY-Bdg.js → approvals-DbYAuNkG.js} +17 -11
- package/dist/approvers-DT4AVyIg.js +52 -0
- package/dist/{arazzo-CTeVuXr5.js → arazzo-BAgw9ap6.js} +2 -2
- package/dist/{arazzo-aoTkqYsJ.js → arazzo-cmvX2jDe.js} +2 -2
- package/dist/authzen/index.d.ts +4 -13
- package/dist/authzen/index.js +16 -50
- package/dist/better-auth/index.d.ts +20 -3
- package/dist/better-auth/index.js +27 -7
- package/dist/better-supabase/index.d.ts +206 -0
- package/dist/better-supabase/index.js +620 -0
- package/dist/bind-CVy3Espv.d.ts +25 -0
- package/dist/bind-VnYn2HKq.js +97 -0
- package/dist/boundary-BM8a_P6E.js +13 -0
- package/dist/boundary-BmHxVH1J.d.ts +15 -0
- package/dist/canonical-json-DemiK4Vp.js +26 -0
- package/dist/{capability-B68OFnZx.js → capability-4qPdO9gk.js} +7 -5
- package/dist/{capability-BDG_nkxt.d.ts → capability-Dq43tT1u.d.ts} +2 -2
- package/dist/catalog/index.d.ts +1 -1
- package/dist/catalog/index.js +1 -1
- package/dist/catalog-BjZSovXQ.js +2 -0
- package/dist/{catalog-doc-BSo_Q_-6.js → catalog-doc-Dsc9y7sd.js} +23 -12
- package/dist/{catalog-B0E6bggc.js → catalog-h2tlKFkq.js} +2 -2
- package/dist/{catalog-H-vFGwWK.js → catalog-z8q6bi_r.js} +4 -4
- package/dist/claude-agent/index.d.ts +5 -14
- package/dist/claude-agent/index.js +4 -10
- package/dist/clerk/index.d.ts +12 -3
- package/dist/clerk/index.js +26 -5
- package/dist/cli/bin.js +1 -1
- package/dist/cli/index.d.ts +3 -3
- package/dist/cli/index.js +2 -2
- package/dist/clients-C7zLFuWT.d.ts +12 -0
- package/dist/clients-DjU5ABMy.js +23 -0
- package/dist/cloud/index.d.ts +4 -5
- package/dist/cloud/index.js +20 -6
- package/dist/{cloud-BNQTrmrr.js → cloud-BGhHS-lY.js} +2 -2
- package/dist/{cloud-Cuxr6Czw.js → cloud-Bo3e9Ak3.js} +7 -6
- package/dist/{collect-O-tOtbnk.js → collect-DDDv_ESs.js} +194 -42
- package/dist/collect-DLlujdHC.js +83 -0
- package/dist/collect-rG2IQ3t0.js +2 -0
- package/dist/{commands-BMh_Etm2.js → commands-DWOrSQic.js} +16 -12
- package/dist/compile/index.d.ts +3 -0
- package/dist/compile/index.js +2 -0
- package/dist/{compile-D22iwvuV.js → compile-Ba4blROR.js} +85 -30
- package/dist/compile-CNnl6uIt.d.ts +94 -0
- package/dist/config-BJiM-xAm.js +157 -0
- package/dist/config-Bn00Z3ks.js +36 -0
- package/dist/config-report-Dw_JcUqT.js +44 -0
- package/dist/{connection-JvqwHODC.js → connection-CU8MQxRi.js} +1 -1
- package/dist/{context-CKEAabd7.js → context-Ttp0ekMT.js} +36 -27
- package/dist/convex/index.d.ts +9 -4
- package/dist/convex/index.js +14 -4
- package/dist/create-BWyx3jua.d.ts +245 -0
- package/dist/create-CPhDL6qx.js +253 -0
- package/dist/{credential-B_rkUNlY.js → credential-8P81ff4u.js} +26 -46
- package/dist/delegation-D8pUEuce.js +103 -0
- package/dist/{describe-Bt1YD5uS.js → describe-B_gVZEVi.js} +10 -15
- package/dist/{describe-DernyItf.d.ts → describe-PdoqESvg.d.ts} +9 -10
- package/dist/{diff-x5Il8LVa.js → diff-B8Tunc3o.js} +44 -11
- package/dist/{diff-BUzf5Poi.js → diff-CzqLxZ77.js} +2 -2
- package/dist/doctor-WpvG6p4k.js +1194 -0
- package/dist/doctor-collect-ByfYlNv1.js +664 -0
- package/dist/{doctor-Bqo6FwUa.js → doctor-rOr-4ZfS.js} +2 -2
- package/dist/drizzle/index.d.ts +4 -3
- package/dist/drizzle/index.js +3 -3
- package/dist/elysia/index.d.ts +6 -28
- package/dist/elysia/index.js +16 -60
- package/dist/{errors-DSMnAVNa.js → errors-CWfoNg9N.js} +8 -4
- package/dist/{errors-Iy71s7Oj.js → errors-Cz5zn0mW.js} +17 -71
- package/dist/{errors-DbZ1YOnb.d.ts → errors-DAqevpNk.d.ts} +8 -21
- package/dist/{errors-m3u9TmDu.js → errors-DRnGZHzF.js} +4 -1
- package/dist/evaluation-items-CfCCejQd.js +40 -0
- package/dist/{evaluations-DsAU9ppl.js → evaluations-tz6D9A2G.js} +28 -46
- package/dist/eve/index.d.ts +6 -15
- package/dist/eve/index.js +7 -12
- package/dist/expiry-BoBJ8Tvc.js +7 -0
- package/dist/express/index.d.ts +6 -29
- package/dist/express/index.js +28 -58
- package/dist/fastify/index.d.ts +6 -29
- package/dist/fastify/index.js +16 -65
- package/dist/fields-BMl1RxoX.js +26 -0
- package/dist/{files-B_wAGYmt.js → files-DWUN9PzI.js} +5 -1
- package/dist/{fixtures-Cvaw0gDG.js → fixtures-ht28Cplr.js} +5 -2
- package/dist/{freeze-y1_jQL4v.js → freeze-CrU8nQeX.js} +31 -2
- package/dist/{from-snapshot-BMGnhmgv.js → from-snapshot-C3sHpZNS.js} +65 -48
- package/dist/{grantee-Bv04_Vzf.js → grantee-Cv5Sogfv.js} +88 -7
- package/dist/graph-sql-B-dsAT6j.d.ts +29 -0
- package/dist/{headers-B5RRv3Xm.js → headers-BZSDieNT.js} +1 -1
- package/dist/{headers-C8SHQccD.d.ts → headers-Cf-zWvsO.d.ts} +1 -1
- package/dist/helpers-H_KARDVC.js +421 -0
- package/dist/hono/index.d.ts +6 -28
- package/dist/hono/index.js +11 -43
- package/dist/{hosted-BwKQ1BTA.js → hosted-DJQw46hn.js} +6 -5
- package/dist/{http-GgVApX1k.js → http-BIhmLfCc.js} +4 -20
- package/dist/http-SO7qGttf.js +44 -0
- package/dist/ids-DC4V4CFu.js +18 -0
- package/dist/{index-LF_hS-yn.d.ts → index-A0YNL8jZ.d.ts} +1 -1
- package/dist/index-CtE07CBF.d.ts +6 -0
- package/dist/{index-xIxJLwOQ.d.ts → index-D3vbkoSI.d.ts} +75 -7
- package/dist/{index-CltcQ4Bf.d.ts → index-D6_cSa1z.d.ts} +68 -5
- package/dist/index.d.ts +14 -9
- package/dist/index.js +22 -17
- package/dist/instance-options-BK_rNOSC.d.ts +11 -0
- package/dist/instance-options-Dhhcnm3y.js +22 -0
- package/dist/{parse-DPfIvFMU.js → json-schema-BsAflUGW.js} +28 -57
- package/dist/jwt/index.d.ts +10 -6
- package/dist/jwt/index.js +32 -13
- package/dist/kernel-GSQnMz3O.js +208 -0
- package/dist/kysely/index.d.ts +4 -3
- package/dist/kysely/index.js +3 -3
- package/dist/live-options-H2eG8dfJ.js +60 -0
- package/dist/{load-BZ24CpZP.js → load-DhWEB9P1.js} +16 -10
- package/dist/load-DwJUafYN.js +2 -0
- package/dist/manifest-iaoBXHKc.js +783 -0
- package/dist/manifest-uDS9y5de.d.ts +206 -0
- package/dist/{map-DowSO96j.js → map-AcxK9o_D.js} +4 -15
- package/dist/{map-claims-B_6I1Rn-.js → map-claims-Z4QZOiFF.js} +13 -4
- package/dist/map-error-pgUWNNtG.js +23 -0
- package/dist/may-use-DndQ5tQ-.js +77 -0
- package/dist/mcp/index.d.ts +54 -22
- package/dist/mcp/index.js +218 -178
- package/dist/nest/index.d.ts +26 -31
- package/dist/nest/index.js +75 -84
- package/dist/next/client.d.ts +7 -8
- package/dist/next/client.js +11 -9
- package/dist/next/index.d.ts +30 -47
- package/dist/next/index.js +45 -60
- package/dist/next/plugin.d.ts +1 -1
- package/dist/next/plugin.js +32 -1
- package/dist/node/index.d.ts +9 -28
- package/dist/node/index.js +11 -43
- package/dist/openai/index.d.ts +6 -15
- package/dist/openai/index.js +7 -20
- package/dist/openapi/index.d.ts +87 -4
- package/dist/openapi/index.js +3 -3
- package/dist/{openapi-CwePaF7C.js → openapi-Bbg4wGVL.js} +3 -3
- package/dist/{openapi-DcqM8nU7.js → openapi-BqRBvAng.js} +237 -5
- package/dist/{openapi-DWD0Uc_4.js → openapi-CqqEbHsL.js} +5 -5
- package/dist/{openapi-import-BWRUNNeW.js → openapi-import-cmwetbzw.js} +1 -1
- package/dist/{openapi-schema-D8j_T9tO.js → openapi-schema--ARjyfQr.js} +2 -2
- package/dist/orpc/index.d.ts +15 -28
- package/dist/orpc/index.js +74 -117
- package/dist/otel/index.d.ts +2 -2
- package/dist/parse-BuyjxhZV.js +53 -0
- package/dist/parse-local-manifest-BkGQNzAh.d.ts +10 -0
- package/dist/parse-local-manifest-BrMozJCo.js +36 -0
- package/dist/pdp/index.d.ts +2 -2
- package/dist/pdp/index.js +14 -127
- package/dist/{permdock-yoZ7EjxX.js → permdock-CzqXjCpU.js} +641 -126
- package/dist/{permissions-B8vCg_bI.d.ts → permissions-CDgvKG5L.d.ts} +2 -2
- package/dist/{permissions-CofRqesP.js → permissions-CpZrivYv.js} +3 -3
- package/dist/{permissions-DC7GROPA.js → permissions-DVoF6dMt.js} +77 -15
- package/dist/{permissions-B4apnahQ.d.ts → permissions-DttnqqxC.d.ts} +34 -5
- package/dist/{pins-CqE1jZuF.js → pins-8ILTftC9.js} +2 -2
- package/dist/{policy-CInmNGkM.d.ts → policy-CFZAr_Al.d.ts} +330 -37
- package/dist/{policy-Cq2fB8Q5.d.ts → policy-D8UFyERw.d.ts} +2 -2
- package/dist/{policy-ANp36PnI.js → policy-xyWg7QdR.js} +173 -22
- package/dist/powersync-A8x_Yyvh.js +2 -0
- package/dist/powersync-C7kLglqX.js +540 -0
- package/dist/powersync-yW1UUFzp.js +62 -0
- package/dist/prisma/index.d.ts +3 -2
- package/dist/prisma/index.js +45 -20
- package/dist/{problem-Cgp9-KZA.js → problem-B43ztKIm.js} +74 -109
- package/dist/problem-details-CkL50EEf.d.ts +7 -0
- package/dist/problem-details-_AMOYz-w.js +144 -0
- package/dist/{protected-BuPAycJT.d.ts → protected-CNsMzrVd.d.ts} +5 -4
- package/dist/protected-Cs6kTaHo.js +100 -0
- package/dist/protected-bZs6rbT1.d.ts +40 -0
- package/dist/{provider-wTAUda2v.d.ts → provider-Cjr62YMV.d.ts} +1 -1
- package/dist/provider-DLzpb2Xz.js +52 -0
- package/dist/provider-store-DNyC1y48.js +43 -0
- package/dist/react/index.d.ts +7 -5
- package/dist/react/index.js +5 -4
- package/dist/react/provider-client.d.ts +1 -1
- package/dist/react/provider-client.js +1 -1
- package/dist/react-native/index.d.ts +166 -10
- package/dist/react-native/index.js +607 -85
- package/dist/{refs-ChLSxbST.js → refs-KYb4ntaz.js} +4 -3
- package/dist/{relations-r4soIFZB.js → relations-cjOL01hZ.js} +110 -31
- package/dist/replay-BsOyTrdV.js +28 -0
- package/dist/replay-sqBGMnNs.d.ts +5 -0
- package/dist/resource-ref-CaS9yVPi.js +20 -0
- package/dist/revocations-Bhr0aMIC.js +27 -0
- package/dist/rls-BMNydpHk.js +2521 -0
- package/dist/{rls-BOFU3chG.js → rls-CEvs64Kj.js} +8 -3
- package/dist/rls-fields-BMasREhR.js +3502 -0
- package/dist/{rls-graph-DxQ39PJj.js → rls-graph-pc9fuXYm.js} +100 -36
- package/dist/{rls-import-CXV6LBlS.js → rls-import-D8F7j35Q.js} +7 -6
- package/dist/{rls-migrate-DrFPbf8p.js → rls-migrate-7wikfrVI.js} +6 -5
- package/dist/rls-ownership-B5Lkq62W.js +843 -0
- package/dist/rls-shared-aOQ4hGTO.js +493 -0
- package/dist/rls-verify-D8B7dOTh.js +745 -0
- package/dist/{row-check-BajTgQZ-.d.ts → row-check-Dt35czLy.d.ts} +12 -15
- package/dist/rpc-DOK54_CS.js +89 -0
- package/dist/{run-CJ6UvS3a.js → run-YXCJDNYk.js} +31 -12
- package/dist/runtime-CB8gQFvS.js +11 -0
- package/dist/{saas-BRrZzJkM.js → saas-Bj_FFkjN.js} +17 -7
- package/dist/{schema-P8yoma__.js → schema-D3MnDMxa.js} +8 -4
- package/dist/scim/index.d.ts +1 -1
- package/dist/scim/index.js +1 -1
- package/dist/{scim-9NFa0XVZ.js → scim-BFGBBAqR.js} +1 -1
- package/dist/{scopes-cKVp_WTw.js → scopes-CVdPrWN-.js} +35 -8
- package/dist/server/index.d.ts +22 -11
- package/dist/server/index.js +11 -8
- package/dist/{credentials-BgRX3WEg.js → server-DlZ28Oz5.js} +1 -1
- package/dist/shared-DRu8VFVM.js +123 -0
- package/dist/{sink-Cfr9-7YC.d.ts → sink-Gs6TTVa-.d.ts} +1 -1
- package/dist/skills-Biq5uup3.js +192 -0
- package/dist/{skills-C_lpV4Jx.js → skills-JaymNCdK.js} +10 -4
- package/dist/{snapshot-DE8dE_hz.js → snapshot-Enxd0n-s.js} +138 -214
- package/dist/snapshot-cache-D6jLelln.js +44 -0
- package/dist/snapshot-cache-DwQH8ufx.d.ts +45 -0
- package/dist/solid/index.d.ts +39 -9
- package/dist/solid/index.js +76 -85
- package/dist/sources-B0UvrJ21.js +534 -0
- package/dist/sources-BcJgw0lt.d.ts +192 -0
- package/dist/{sql-Bll1d7y0.js → sql-xdg5f4Fi.js} +6 -1
- package/dist/{src-CTNT-PBW.js → src-CSbUiozF.js} +149 -11
- package/dist/ssf/index.d.ts +3 -2
- package/dist/ssf/index.js +3 -29
- package/dist/store-DMAWwAJV.js +371 -0
- package/dist/{store-Bq90395i.d.ts → store-D_pjn91-.d.ts} +13 -4
- package/dist/store-options-yO0V6gRn.js +716 -0
- package/dist/{stores-CkG2HrSg.js → stores-4_sqiOA1.js} +10 -63
- package/dist/{stores-CvOnu_2T.d.ts → stores-DVR5NHLE.d.ts} +20 -9
- package/dist/{stream-Q_xlB6Ko.js → stream-DDpvynSB.js} +16 -2
- package/dist/{stream-BhMfZOwr.d.ts → stream-DlvG2hs-.d.ts} +2 -2
- package/dist/subject-settings-BwBmCdTR.js +128 -0
- package/dist/supabase/index.d.ts +122 -6
- package/dist/supabase/index.js +4 -3
- package/dist/supabase/middleware.d.ts +32 -27
- package/dist/supabase/middleware.js +56 -22
- package/dist/{supabase-D8UlBA7Z.js → supabase-CwafwxxF.js} +3 -3
- package/dist/{supabase-DTxGtYdi.js → supabase-DTBaC20O.js} +345 -367
- package/dist/supabase-config-Bs44dare.js +472 -0
- package/dist/{supabase-hook-D_OqGb_V.js → supabase-hook-BuxLQLpU.js} +994 -708
- package/dist/supabase-hook-D5qVd_D8.js +2 -0
- package/dist/supautils-DYZnzRWV.js +91 -0
- package/dist/svelte/PermissionBoundary.svelte +37 -0
- package/dist/svelte/Protected.svelte +8 -2
- package/dist/svelte/index.d.ts +9 -6
- package/dist/svelte/index.js +61 -7
- package/dist/svelte/runtime.d.ts +4 -3
- package/dist/svelte/runtime.js +4 -3
- package/dist/svelte/source.d.ts +9 -6
- package/dist/svelte/source.js +7 -4
- package/dist/terminal/index.d.ts +6 -14
- package/dist/terminal/index.js +44 -32
- package/dist/testing/index.d.ts +105 -16
- package/dist/testing/index.js +504 -40
- package/dist/testing/saas/index.d.ts +4 -4
- package/dist/testing/saas/index.js +2 -2
- package/dist/testing/saas/permissions.d.ts +1 -1
- package/dist/testing/saas/permissions.js +1 -1
- package/dist/token-CIWxJOCh.js +50 -0
- package/dist/trpc/index.d.ts +7 -26
- package/dist/trpc/index.js +17 -100
- package/dist/types-9AWO9EGt.d.ts +197 -0
- package/dist/{types-D6Z6-rKJ.d.ts → types-BPxgszlT.d.ts} +260 -22
- package/dist/{types-BvBk1wgn.d.ts → types-BVyImy5I.d.ts} +5 -3
- package/dist/{types-CxquUByI.d.ts → types-Bvc4oWT6.d.ts} +1 -1
- package/dist/types-C3s9xVJG.d.ts +48 -0
- package/dist/{types-BBK-exvJ.d.ts → types-Dnemwd77.d.ts} +7 -1
- package/dist/types-kxznc7cn.d.ts +52 -0
- package/dist/{types-B8w5PFtS.d.ts → types-vKyAMtcX.d.ts} +17 -1
- package/dist/{types-B9dmiiab.d.ts → types-vNSDF7Dq.d.ts} +1 -1
- package/dist/{sources-DOpW5Ila.d.ts → types-yF5LmQVD.d.ts} +86 -127
- package/dist/unplugin/index.d.ts +1 -1
- package/dist/unplugin/index.js +10 -9
- package/dist/{usage-1ozMubLY.js → usage-D07N9QJ3.js} +2 -2
- package/dist/usage-D82l1CwK.js +2 -0
- package/dist/{usage-CUnK6Ou9.js → usage-DG1oeCca.js} +225 -62
- package/dist/validation-error-CnjZ4t92.js +33 -0
- package/dist/validation-error-CtF23SrC.d.ts +26 -0
- package/dist/{validity-D0uRmJKJ.js → validity-uOp8UpYn.js} +57 -36
- package/dist/{verifier-B90agnhl.js → verifier-BxBo6I44.js} +10 -5
- package/dist/version-BvacVTZR.js +17 -0
- package/dist/views-Soe7bGSC.js +95 -0
- package/dist/{vocabulary-Dk0fs-Jk.d.ts → vocabulary-DA8a8zif.d.ts} +1 -1
- package/dist/vue/index.d.ts +35 -13
- package/dist/vue/index.js +86 -93
- package/dist/watch-Etoa_Mvr.js +2 -0
- package/dist/watch-UihESpsJ.js +128 -0
- package/dist/webmcp/index.d.ts +2 -2
- package/dist/webmcp/index.js +7 -13
- package/package.json +62 -36
- package/schemas/approval-request-v1.json +175 -0
- package/schemas/catalog-v1.json +29 -1
- package/schemas/supabase-claims-v1.json +6 -1
- package/schemas/supabase-manifest-v1.json +150 -6
- package/skills/permdock/SKILL.md +7 -7
- package/skills/permdock-agents/SKILL.md +5 -5
- package/skills/permdock-agents/references/runtimes.md +12 -10
- package/skills/permdock-approvals/SKILL.md +4 -4
- package/skills/permdock-approvals/references/stores-and-resume.md +15 -4
- package/skills/permdock-audit/SKILL.md +3 -3
- package/skills/permdock-credentials/SKILL.md +4 -4
- package/skills/permdock-credentials/references/tokens-keys-links.md +7 -2
- package/skills/permdock-data/SKILL.md +4 -4
- package/skills/permdock-data/references/queries-and-rls.md +14 -6
- package/skills/permdock-data/references/relationships.md +5 -3
- package/skills/permdock-tenancy/SKILL.md +9 -6
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +14 -8
- package/skills/permdock-wire/SKILL.md +4 -4
- package/skills/permdock-wire/references/adapters.md +42 -30
- package/skills/permdock-wire/references/policy-options.md +5 -3
- package/dist/approvers-aH0PJkmf.js +0 -20
- package/dist/catalog-idsHc0vo.js +0 -2
- package/dist/collect-B7_TIfmg.js +0 -49
- package/dist/collect-fRPAQIWb.js +0 -2
- package/dist/compile-CQbvyIBr.d.ts +0 -30
- package/dist/config-BPYmjTOp.js +0 -26
- package/dist/context-gtAwAILx.js +0 -6
- package/dist/create-BKF3dgFk.d.ts +0 -162
- package/dist/create-CSkhDU9T.js +0 -222
- package/dist/doctor-BGle8Gqi.js +0 -1314
- package/dist/helpers-DnSOrhB9.js +0 -682
- package/dist/index-fG6NSXlc.d.ts +0 -9
- package/dist/kernel-YOGCWNLf.js +0 -211
- package/dist/load-QOOm1z70.js +0 -2
- package/dist/manifest-DDQbC6K0.d.ts +0 -118
- package/dist/map-error-RJ3ZsUyA.js +0 -16
- package/dist/may-use-CnFEjVEp.js +0 -37
- package/dist/pg-4GJWDUxG.js +0 -953
- package/dist/plugin-4gr7IxFJ.js +0 -65
- package/dist/protected-B4K3BKoX.js +0 -138
- package/dist/protected-wY7nXhY9.d.ts +0 -23
- package/dist/provider-_wx46HpB.js +0 -48
- package/dist/rls-CJVja9Ww.js +0 -2119
- package/dist/rls-helpers-BxRuif0l.js +0 -1346
- package/dist/runtime-DP9ttFh1.js +0 -27
- package/dist/skills-Dx3QLnMY.js +0 -110
- package/dist/source-DoBBYJi2.js +0 -6
- package/dist/sources-DVHM3Mwq.js +0 -284
- package/dist/store-C6hFW6ta.js +0 -508
- package/dist/subject-settings-xt7V845g.js +0 -82
- package/dist/supabase-hook-DW-xP4hU.js +0 -2
- package/dist/types-DbWdEfcm.d.ts +0 -87
- package/dist/types-bva3i4Gf.d.ts +0 -98
- package/dist/types-i4BAOvdb.d.ts +0 -97
- package/dist/usage-PyEor6GA.js +0 -2
- package/dist/version-CnSh33Fj.js +0 -16
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import { n as isReadonlyArray, r as sole, t as compact } from "./compact-y6vfu2_f.js";
|
|
2
|
-
import { n as freezeDeep, t as freezeCopy } from "./freeze-
|
|
3
|
-
import {
|
|
4
|
-
import { a as hasConditionOp } from "./refs-
|
|
5
|
-
import { d as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { a as
|
|
9
|
-
import {
|
|
2
|
+
import { n as freezeDeep, t as freezeCopy } from "./freeze-CrU8nQeX.js";
|
|
3
|
+
import { E as ownGet, T as isForbiddenKey, _ as listPermissions, a as formerKeys, c as getResource, d as isEdgeRelation, f as isFieldRelation, i as findPermission, m as isPrincipalRelation, o as formerScopes, r as expandRelation } from "./permissions-DVoF6dMt.js";
|
|
4
|
+
import { a as hasConditionOp } from "./refs-KYb4ntaz.js";
|
|
5
|
+
import { d as normalizeApproval, f as normalizeAssurance, g as applyQuota, i as declaredRoleNames$1, l as levelCondition, m as role, n as assertApprovalRelations, s as grantList, u as levelNames, v as normalizeWhere } from "./policy-xyWg7QdR.js";
|
|
6
|
+
import { n as PermDockValidationError } from "./validation-error-CnjZ4t92.js";
|
|
7
|
+
import { a as wireDenials } from "./problem-details-_AMOYz-w.js";
|
|
8
|
+
import { a as deniedMessage, i as approvalMessage, n as PermDockDeniedError, t as PermDockApprovalRequiredError } from "./errors-Cz5zn0mW.js";
|
|
9
|
+
import { n as sameId } from "./ids-DC4V4CFu.js";
|
|
10
|
+
import { a as normalizeMemberships, c as rootScope, d as scopeList, f as subjectForPermission, i as keepsPermission, l as scopeChain, m as tenantOf, o as resolveScope, s as rootMembershipId, u as scopeIdOf } from "./scopes-CVdPrWN-.js";
|
|
11
|
+
import { a as whereFromGrants, c as rowIdOf, d as isArazzoSimulateInput, f as simulateArazzo, i as signSnapshot, l as rowValues, p as evaluateCondition, r as snapshotGrant, s as isRowPair, t as buildSnapshot } from "./snapshot-Enxd0n-s.js";
|
|
10
12
|
import { t as parseDuration } from "./duration-xK2jGFVc.js";
|
|
11
|
-
import {
|
|
13
|
+
import { D as synthesiseRole, E as listRoles, S as findRole, _ as resourceRoleCondition, a as authenticated, d as matchGrantee, i as assurance, m as relationCondition, o as combineWhere, r as asGrantee, s as flattenGrantee, w as isRole } from "./grantee-Cv5Sogfv.js";
|
|
14
|
+
import { t as canonicalJson } from "./canonical-json-DemiK4Vp.js";
|
|
15
|
+
import { n as pickVisible, r as sanitizeContext, t as grantCoversField } from "./fields-BMl1RxoX.js";
|
|
16
|
+
import { a as isMembershipExpired, d as partitionsOf, f as relatesTo, g as tenantsOf, h as scopeField, i as inTeam, o as matchScopedMembership, p as resolveActiveTenant, r as heldRoleNamesIn, t as isActive, u as nowSeconds } from "./validity-uOp8UpYn.js";
|
|
12
17
|
import { t as requiresApproval } from "./approval-required-TzNB9oQA.js";
|
|
13
|
-
import { r as mergeHostedGrants, t as isPortableCondition } from "./hosted-
|
|
18
|
+
import { r as mergeHostedGrants, t as isPortableCondition } from "./hosted-DJQw46hn.js";
|
|
14
19
|
import { n as isThenable, t as ignoreRejection } from "./thenable-okc2xDsj.js";
|
|
15
20
|
import { t as byCodePoint } from "./compare-pfyVnBjE.js";
|
|
16
|
-
import { a as relationWalk, i as relationReader, n as pendingRelations, o as resolveRelated, r as relationId } from "./relations-
|
|
21
|
+
import { a as relationWalk, i as relationReader, n as pendingRelations, o as resolveRelated, r as relationId } from "./relations-cjOL01hZ.js";
|
|
22
|
+
import { i as requiresStandIn, n as coversRequirements, r as delegatedPermissions, t as coveredByDelegation } from "./delegation-D8pUEuce.js";
|
|
23
|
+
import { t as resourceIdOf } from "./resource-ref-CaS9yVPi.js";
|
|
24
|
+
import { n as payloadDigest, r as versionOf, t as decisionToken } from "./token-CIWxJOCh.js";
|
|
17
25
|
import { i as isSubject, r as isPrincipal, t as anonymousSubject } from "./subject-C6dLxqHU.js";
|
|
18
26
|
//#region src/core/approval-policies.ts
|
|
27
|
+
/** The denial detail when an `ApprovalPolicySource` fails: every call an allow would grant denies with reason `approval`. */
|
|
28
|
+
const APPROVAL_POLICY_UNAVAILABLE = "approval-policy-unavailable";
|
|
19
29
|
function memoryApprovalPolicies(policies) {
|
|
20
30
|
const entries = [...policies];
|
|
21
31
|
return Object.freeze({ approvalPoliciesFor(query) {
|
|
@@ -28,17 +38,13 @@ function isRecord(value) {
|
|
|
28
38
|
function isStringList(value) {
|
|
29
39
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
30
40
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* permission the policy declares (it applies to nothing), `undefined` when
|
|
34
|
-
* it is invalid.
|
|
35
|
-
*/
|
|
36
|
-
function loadOne(policy, raw) {
|
|
37
|
-
if (!isRecord(raw) || typeof raw["permission"] !== "string") return;
|
|
41
|
+
function checkOne(policy, raw) {
|
|
42
|
+
if (!isRecord(raw) || typeof raw["permission"] !== "string") return { problem: "invalid" };
|
|
38
43
|
const permission = findPermission(policy.permissions, raw["permission"]);
|
|
39
|
-
if (permission === void 0) return
|
|
44
|
+
if (permission === void 0) return { problem: "unknown-permission" };
|
|
40
45
|
const { tenant, actors, where, check, approval } = raw;
|
|
41
|
-
if (tenant !== void 0 && typeof tenant !== "string" || actors !== void 0 && !isStringList(actors) || where !== void 0 && !isPortableCondition(where) || check !== void 0 && !isPortableCondition(check) ||
|
|
46
|
+
if (tenant !== void 0 && typeof tenant !== "string" || actors !== void 0 && !isStringList(actors) || where !== void 0 && !isPortableCondition(where) || check !== void 0 && !isPortableCondition(check) || approval !== "human" && !isRecord(approval)) return { problem: "invalid" };
|
|
47
|
+
if (where !== void 0 && permission.kind !== "instance") return { problem: "where-on-collection" };
|
|
42
48
|
let normalized;
|
|
43
49
|
try {
|
|
44
50
|
normalized = normalizeApproval(approval, permission.key);
|
|
@@ -47,18 +53,47 @@ function loadOne(policy, raw) {
|
|
|
47
53
|
approval: normalized
|
|
48
54
|
}], policy.resources);
|
|
49
55
|
} catch {
|
|
50
|
-
return;
|
|
56
|
+
return { problem: "invalid" };
|
|
51
57
|
}
|
|
52
|
-
if (normalized === void 0) return;
|
|
53
|
-
if (normalized !== "human" && normalized.staleOn !== void 0 && (permission.kind !== "instance" || policy.resources.get(permission.resource)?.version === void 0)) return;
|
|
54
|
-
return compact({
|
|
58
|
+
if (normalized === void 0) return { problem: "invalid" };
|
|
59
|
+
if (normalized !== "human" && normalized.staleOn !== void 0 && (permission.kind !== "instance" || policy.resources.get(permission.resource)?.version === void 0)) return { problem: "stale-on-without-version" };
|
|
60
|
+
return { entry: compact({
|
|
55
61
|
permission: permission.key,
|
|
56
62
|
tenant,
|
|
57
63
|
actors: actors === void 0 ? void 0 : new Set(actors),
|
|
58
64
|
where: where === void 0 ? void 0 : normalizeWhere(where),
|
|
59
65
|
check: check === void 0 ? void 0 : normalizeWhere(check),
|
|
60
66
|
approval: normalized
|
|
61
|
-
});
|
|
67
|
+
}) };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Checks one entry against the policy the way `approvalPoliciesFor` loads
|
|
71
|
+
* it, for an application to refuse a bad entry when it is saved instead of
|
|
72
|
+
* finding every call denied. Never throws.
|
|
73
|
+
*/
|
|
74
|
+
function validateApprovalPolicy(policy, entry) {
|
|
75
|
+
try {
|
|
76
|
+
const checked = checkOne(policy, entry);
|
|
77
|
+
return "problem" in checked ? {
|
|
78
|
+
ok: false,
|
|
79
|
+
problem: checked.problem
|
|
80
|
+
} : { ok: true };
|
|
81
|
+
} catch {
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
problem: "invalid"
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* One raw entry checked against the policy: `null` when it names no
|
|
90
|
+
* permission the policy declares (it applies to nothing), `undefined` when
|
|
91
|
+
* it does not load.
|
|
92
|
+
*/
|
|
93
|
+
function loadOne(policy, raw) {
|
|
94
|
+
const checked = checkOne(policy, raw);
|
|
95
|
+
if ("entry" in checked) return checked.entry;
|
|
96
|
+
return checked.problem === "unknown-permission" ? null : void 0;
|
|
62
97
|
}
|
|
63
98
|
function loadAll(policy, raw) {
|
|
64
99
|
if (!Array.isArray(raw)) return "failed";
|
|
@@ -103,6 +138,17 @@ function asStages(approval) {
|
|
|
103
138
|
quorum: approval.quorum
|
|
104
139
|
})];
|
|
105
140
|
}
|
|
141
|
+
/** An entry's stages, each carrying the entry's escalation unless it has its own. */
|
|
142
|
+
function entryStages(approval) {
|
|
143
|
+
const stages = asStages(approval);
|
|
144
|
+
if (approval === "human" || approval.escalation === void 0) return stages;
|
|
145
|
+
const escalation = approval.escalation;
|
|
146
|
+
return stages.map((stage) => stage.escalation === void 0 ? compact({
|
|
147
|
+
by: stage.by,
|
|
148
|
+
quorum: stage.quorum,
|
|
149
|
+
escalation
|
|
150
|
+
}) : stage);
|
|
151
|
+
}
|
|
106
152
|
function shorter(a, b) {
|
|
107
153
|
if (a === void 0) return b;
|
|
108
154
|
if (b === void 0) return a;
|
|
@@ -124,8 +170,9 @@ function tightenApproval(code, entries, input) {
|
|
|
124
170
|
let distinct = false;
|
|
125
171
|
let staleOn;
|
|
126
172
|
let ttl;
|
|
127
|
-
for (const part of parts) {
|
|
128
|
-
|
|
173
|
+
for (const [index, part] of parts.entries()) {
|
|
174
|
+
const fromCode = code !== void 0 && index === 0;
|
|
175
|
+
for (const stage of fromCode ? asStages(part) : entryStages(part)) {
|
|
129
176
|
const key = JSON.stringify(stage);
|
|
130
177
|
if (!seen.has(key)) {
|
|
131
178
|
seen.add(key);
|
|
@@ -173,7 +220,8 @@ function identity(membership) {
|
|
|
173
220
|
membership.member?.group ?? null,
|
|
174
221
|
[...membership.eligible ?? []].toSorted(),
|
|
175
222
|
membership.managedBy ?? null,
|
|
176
|
-
[...membership.entitlements ?? []].toSorted()
|
|
223
|
+
[...membership.entitlements ?? []].toSorted(),
|
|
224
|
+
membership.keep === void 0 ? null : [...membership.keep].toSorted()
|
|
177
225
|
]);
|
|
178
226
|
}
|
|
179
227
|
/**
|
|
@@ -245,7 +293,8 @@ function asMembershipSource(input) {
|
|
|
245
293
|
/**
|
|
246
294
|
* Trust the memberships the verified token carries, and read `source` only
|
|
247
295
|
* when the token marks them truncated. With `version`, a token minted before
|
|
248
|
-
* the latest membership change is stale for the policy's `fresh` permissions
|
|
296
|
+
* the latest membership change is stale for the policy's `fresh` permissions,
|
|
297
|
+
* or with `onStale: 'reread'` has its memberships read from `source` instead.
|
|
249
298
|
*/
|
|
250
299
|
function claimsFirst(source, options = {}) {
|
|
251
300
|
const inner = asMembershipSource(source);
|
|
@@ -254,6 +303,7 @@ function claimsFirst(source, options = {}) {
|
|
|
254
303
|
membershipsFor: (principal, query) => inner.membershipsFor(principal, query),
|
|
255
304
|
...inner.list === void 0 ? {} : { list: (query) => inner.list?.(query) ?? [] },
|
|
256
305
|
...version === void 0 ? {} : { version },
|
|
306
|
+
...options.onStale === "reread" ? { onStale: "reread" } : {},
|
|
257
307
|
claimsFirst: true
|
|
258
308
|
};
|
|
259
309
|
}
|
|
@@ -261,6 +311,33 @@ function claimsFirst(source, options = {}) {
|
|
|
261
311
|
function isExternallyManaged(membership) {
|
|
262
312
|
return membership.managedBy === "idp";
|
|
263
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* How many principals hold `role` in one scope instance now, from the
|
|
316
|
+
* source's member list (`MembershipSource.list`): live memberships of exactly
|
|
317
|
+
* that scope and instance, each principal counted once. The `holders` a
|
|
318
|
+
* `decideRoleChange` with `min`, `max` or `transferOnly` needs. `undefined`
|
|
319
|
+
* when the source cannot list or the read fails, which that check denies.
|
|
320
|
+
*/
|
|
321
|
+
async function countHolders(source, query) {
|
|
322
|
+
const composed = asMembershipSource(source);
|
|
323
|
+
if (composed.list === void 0) return;
|
|
324
|
+
const role = typeof query.role === "string" ? query.role : query.role.key;
|
|
325
|
+
try {
|
|
326
|
+
const entries = await composed.list({
|
|
327
|
+
scope: query.scope,
|
|
328
|
+
id: query.id
|
|
329
|
+
});
|
|
330
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
331
|
+
const holders = /* @__PURE__ */ new Set();
|
|
332
|
+
for (const entry of entries) {
|
|
333
|
+
const membership = entry.membership;
|
|
334
|
+
if (membership.scope === query.scope && membership.id === query.id && membership.roles.includes(role) && (membership.expiresAt === void 0 || membership.expiresAt > now)) holders.add(entry.principal.id);
|
|
335
|
+
}
|
|
336
|
+
return holders.size;
|
|
337
|
+
} catch {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
264
341
|
//#endregion
|
|
265
342
|
//#region src/core/custom-roles.ts
|
|
266
343
|
/** The current key for a stored key, current or former; `undefined` when neither. */
|
|
@@ -269,7 +346,30 @@ function currentKey(policy, raw) {
|
|
|
269
346
|
if (leaf === void 0) return;
|
|
270
347
|
return leaf.key === raw || formerKeys(leaf).includes(raw) ? leaf.key : void 0;
|
|
271
348
|
}
|
|
272
|
-
const GRANT_KEYS = /* @__PURE__ */ new Set([
|
|
349
|
+
const GRANT_KEYS = /* @__PURE__ */ new Set([
|
|
350
|
+
"permission",
|
|
351
|
+
"effect",
|
|
352
|
+
"level"
|
|
353
|
+
]);
|
|
354
|
+
/** `where` narrowed by a level; the empty level (`{}`) leaves it as is. */
|
|
355
|
+
function andWhere(where, level) {
|
|
356
|
+
if (level.op === "and" && level.conditions.length === 0) return where;
|
|
357
|
+
return where === void 0 ? level : {
|
|
358
|
+
op: "and",
|
|
359
|
+
conditions: [where, level]
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
/** A ceiling grant narrowed by a level's condition and marked with the level. */
|
|
363
|
+
function leveled(grant, level, condition) {
|
|
364
|
+
const where = andWhere(grant.where, condition);
|
|
365
|
+
const check = grant.check === void 0 ? void 0 : andWhere(grant.check, condition);
|
|
366
|
+
return freezeDeep({
|
|
367
|
+
...grant,
|
|
368
|
+
level,
|
|
369
|
+
...where === void 0 ? {} : { where },
|
|
370
|
+
...check === void 0 ? {} : { check }
|
|
371
|
+
});
|
|
372
|
+
}
|
|
273
373
|
/**
|
|
274
374
|
* The scope a custom role is held at: `scope` (a name or alias), the second
|
|
275
375
|
* scope for the `team` input shape, else the first scope. `undefined` when
|
|
@@ -372,8 +472,11 @@ function resolveCustomRole(policy, role) {
|
|
|
372
472
|
};
|
|
373
473
|
const included = [];
|
|
374
474
|
const includedKeys = /* @__PURE__ */ new Set();
|
|
375
|
-
const allowKeys = /* @__PURE__ */ new
|
|
475
|
+
const allowKeys = /* @__PURE__ */ new Map();
|
|
376
476
|
const denyKeys = /* @__PURE__ */ new Set();
|
|
477
|
+
const allowAll = (key) => {
|
|
478
|
+
allowKeys.set(key, "all");
|
|
479
|
+
};
|
|
377
480
|
for (const name of Array.isArray(role.includes) ? role.includes : []) {
|
|
378
481
|
if (typeof name !== "string" || !declared.has(name)) {
|
|
379
482
|
drop({
|
|
@@ -390,7 +493,7 @@ function resolveCustomRole(policy, role) {
|
|
|
390
493
|
} else if (ceilingSet.has(grant)) {
|
|
391
494
|
included.push(grant);
|
|
392
495
|
includedKeys.add(key);
|
|
393
|
-
} else if (ceilingKeys.has(key))
|
|
496
|
+
} else if (ceilingKeys.has(key)) allowAll(key);
|
|
394
497
|
else drop({
|
|
395
498
|
permission: key,
|
|
396
499
|
reason: "outside-ceiling"
|
|
@@ -421,7 +524,9 @@ function resolveCustomRole(policy, role) {
|
|
|
421
524
|
to: key
|
|
422
525
|
});
|
|
423
526
|
const effect = entry.effect ?? "allow";
|
|
424
|
-
|
|
527
|
+
const extra = Object.keys(entry).some((name) => !GRANT_KEYS.has(name));
|
|
528
|
+
const level = entry.level;
|
|
529
|
+
if (extra || effect !== "allow" && effect !== "deny" || effect === "deny" && level !== void 0) {
|
|
425
530
|
drop({
|
|
426
531
|
permission: key,
|
|
427
532
|
reason: "condition-not-allowed"
|
|
@@ -433,6 +538,21 @@ function resolveCustomRole(policy, role) {
|
|
|
433
538
|
denyKeys.add(key);
|
|
434
539
|
continue;
|
|
435
540
|
}
|
|
541
|
+
let narrowing;
|
|
542
|
+
if (level !== void 0) {
|
|
543
|
+
const leaf = findPermission(policy.permissions, key);
|
|
544
|
+
const condition = typeof level === "string" && leaf?.kind === "instance" ? levelCondition(policy, leaf.resource, level) : void 0;
|
|
545
|
+
if (condition === void 0) {
|
|
546
|
+
drop({
|
|
547
|
+
permission: key,
|
|
548
|
+
reason: "unknown-level",
|
|
549
|
+
level: String(level)
|
|
550
|
+
});
|
|
551
|
+
denyKeys.add(key);
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
narrowing = [String(level), condition];
|
|
555
|
+
}
|
|
436
556
|
if (!ceilingKeys.has(key)) {
|
|
437
557
|
drop({
|
|
438
558
|
permission: key,
|
|
@@ -440,19 +560,27 @@ function resolveCustomRole(policy, role) {
|
|
|
440
560
|
});
|
|
441
561
|
continue;
|
|
442
562
|
}
|
|
443
|
-
allowKeys.
|
|
563
|
+
const held = allowKeys.get(key);
|
|
564
|
+
if (narrowing === void 0) allowAll(key);
|
|
565
|
+
else if (held === void 0) allowKeys.set(key, new Map([narrowing]));
|
|
566
|
+
else if (held !== "all") held.set(...narrowing);
|
|
444
567
|
}
|
|
445
568
|
const out = [];
|
|
446
569
|
const push = (grant) => {
|
|
447
570
|
if (!out.includes(grant)) out.push(grant);
|
|
448
571
|
};
|
|
449
572
|
for (const grant of included) if (grant.effect === "deny" || !denyKeys.has(grant.permission.key)) push(grant);
|
|
450
|
-
for (const key of allowKeys) {
|
|
573
|
+
for (const [key, levels] of allowKeys) {
|
|
451
574
|
if (denyKeys.has(key) || includedKeys.has(key)) continue;
|
|
452
575
|
const sources = /* @__PURE__ */ new Set();
|
|
453
|
-
for (const grant of ceiling)
|
|
454
|
-
|
|
576
|
+
for (const grant of ceiling) {
|
|
577
|
+
if (grant.permission.key !== key) continue;
|
|
455
578
|
sources.add(soleRole(grant) ?? "");
|
|
579
|
+
if (levels === "all") {
|
|
580
|
+
push(grant);
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
for (const [level, condition] of levels) push(leveled(grant, level, condition));
|
|
456
584
|
}
|
|
457
585
|
for (const grant of all) if (grant.effect === "deny" && grant.permission.key === key && sources.has(soleRole(grant) ?? "")) push(grant);
|
|
458
586
|
}
|
|
@@ -490,12 +618,17 @@ function customGrantsFor(policy, roles) {
|
|
|
490
618
|
}
|
|
491
619
|
return out;
|
|
492
620
|
}
|
|
621
|
+
/** The well-formed tenant (not platform) custom roles; a declared role name is never one. */
|
|
622
|
+
function tenantCustomRoles(policy, roles) {
|
|
623
|
+
const declared = declaredRoleNames$1(policy);
|
|
624
|
+
return roles.filter((role) => wellFormed(role) && !isGlobalCustomRole(role) && !declared.has(role.name));
|
|
625
|
+
}
|
|
493
626
|
/** A membership at the role's scope, inside its tenant (and instance, when pinned), naming it. */
|
|
494
627
|
function holdsCustomRole(membership, role, scopes) {
|
|
495
628
|
if (isGlobalCustomRole(role)) return false;
|
|
496
629
|
const scope = customRoleScope(role, scopes);
|
|
497
630
|
const id = customRoleId(role);
|
|
498
|
-
return scope !== void 0 && membership.scope === scope && tenantOf(membership, scopes)
|
|
631
|
+
return scope !== void 0 && membership.scope === scope && sameId(tenantOf(membership, scopes), role.tenant) && (id === void 0 || sameId(membership.id, id)) && membership.roles.includes(role.name);
|
|
499
632
|
}
|
|
500
633
|
/** Whether `principal.roles` names the platform custom role. */
|
|
501
634
|
function holdsGlobalCustomRole(roles, role) {
|
|
@@ -503,7 +636,7 @@ function holdsGlobalCustomRole(roles, role) {
|
|
|
503
636
|
}
|
|
504
637
|
/** A tenant's custom role inside that tenant, or a platform custom role anywhere. */
|
|
505
638
|
function isCustomRoleName(name, roles, tenant) {
|
|
506
|
-
return roles.some((item) => wellFormed(item) && item.name === name && (isGlobalCustomRole(item) || tenant !== void 0 && item.tenant
|
|
639
|
+
return roles.some((item) => wellFormed(item) && item.name === name && (isGlobalCustomRole(item) || tenant !== void 0 && sameId(item.tenant, tenant)));
|
|
507
640
|
}
|
|
508
641
|
/**
|
|
509
642
|
* The compact JWT form of custom roles for the `memberships[].grants` claim
|
|
@@ -520,7 +653,14 @@ function customRoleClaim(roles, policy) {
|
|
|
520
653
|
const includes = Array.isArray(role.includes) ? role.includes : [];
|
|
521
654
|
const grants = Array.isArray(role.grants) ? role.grants : [];
|
|
522
655
|
for (const name of includes) entries.push(`@${name}`);
|
|
523
|
-
for (const grant of grants)
|
|
656
|
+
for (const grant of grants) {
|
|
657
|
+
if (grant.effect === "deny") {
|
|
658
|
+
entries.push(`-${keyOf(grant.permission)}`);
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
const level = grant.level;
|
|
662
|
+
entries.push(typeof level === "string" && level !== "" ? `${keyOf(grant.permission)}@${level}` : keyOf(grant.permission));
|
|
663
|
+
}
|
|
524
664
|
claim[role.name] = entries;
|
|
525
665
|
}
|
|
526
666
|
return freezeDeep(claim);
|
|
@@ -704,7 +844,7 @@ function activate(policy, subject, input, now = nowSeconds()) {
|
|
|
704
844
|
if (binding === void 0 || activation === void 0) return activationDenied("unknown-role");
|
|
705
845
|
const scopes = scopeList(policy.scopes);
|
|
706
846
|
const scope = resolveScope(scopes, input.scope) ?? input.scope;
|
|
707
|
-
const eligible = (principal.memberships ?? []).find((membership) => membership.scope === scope && membership.id
|
|
847
|
+
const eligible = (principal.memberships ?? []).find((membership) => membership.scope === scope && sameId(membership.id, input.id) && !isMembershipExpired(membership, now) && (membership.eligible ?? []).includes(roleName) && withinAgrees(scopes, membership.within, input.within));
|
|
708
848
|
if (eligible === void 0) return activationDenied("no-membership");
|
|
709
849
|
const within = eligible.within;
|
|
710
850
|
if (activation.justification === "required") {
|
|
@@ -903,20 +1043,30 @@ function matchWriteScope(match, instance, current, next) {
|
|
|
903
1043
|
const moved = match(next);
|
|
904
1044
|
return moved.ok ? first : moved;
|
|
905
1045
|
}
|
|
906
|
-
function isRowPair(value) {
|
|
907
|
-
return value !== null && typeof value === "object" && "current" in value && "next" in value;
|
|
908
|
-
}
|
|
909
1046
|
function isGlobalRole(value) {
|
|
910
1047
|
return value !== null && typeof value === "object" && Reflect.get(value, "scope") === "global";
|
|
911
1048
|
}
|
|
912
1049
|
function onlyGlobal(roles) {
|
|
913
1050
|
return Array.isArray(roles) ? roles.filter(isGlobalRole) : [];
|
|
914
1051
|
}
|
|
915
|
-
|
|
1052
|
+
/** The `customRoles` option as a source: a factory is called with the resolved subject, and a throw reads none. */
|
|
1053
|
+
function roleSourceFor(option, subject, auth) {
|
|
1054
|
+
if (typeof option !== "function") return option;
|
|
1055
|
+
try {
|
|
1056
|
+
return option(subject);
|
|
1057
|
+
} catch {
|
|
1058
|
+
auth.push({
|
|
1059
|
+
reason: "source-threw",
|
|
1060
|
+
source: "customRoles"
|
|
1061
|
+
});
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
function customRolesFor(source, tenants, auth, globalHeld, heldIn) {
|
|
916
1066
|
if (source === void 0) return [];
|
|
917
1067
|
const loaded = [];
|
|
918
1068
|
for (const tenant of tenants) try {
|
|
919
|
-
loaded.push(source.rolesFor(tenant));
|
|
1069
|
+
loaded.push(source.rolesFor(tenant, { held: heldIn(tenant) }));
|
|
920
1070
|
} catch {
|
|
921
1071
|
auth.push({
|
|
922
1072
|
reason: "source-threw",
|
|
@@ -924,8 +1074,8 @@ function customRolesFor(source, tenants, auth, signedIn = false) {
|
|
|
924
1074
|
});
|
|
925
1075
|
loaded.push([]);
|
|
926
1076
|
}
|
|
927
|
-
if (
|
|
928
|
-
const roles = source.globalRoles();
|
|
1077
|
+
if (globalHeld !== void 0 && source.globalRoles !== void 0) try {
|
|
1078
|
+
const roles = source.globalRoles({ held: globalHeld });
|
|
929
1079
|
loaded.push(isThenable(roles) ? roles.then(onlyGlobal) : onlyGlobal(roles));
|
|
930
1080
|
} catch {
|
|
931
1081
|
auth.push({
|
|
@@ -989,6 +1139,87 @@ function expandRoleNames(names, declared, custom, tenant) {
|
|
|
989
1139
|
function declaredRoleNames(policy) {
|
|
990
1140
|
return policy.index.declaredRoles;
|
|
991
1141
|
}
|
|
1142
|
+
const NEVER = {
|
|
1143
|
+
op: "or",
|
|
1144
|
+
conditions: []
|
|
1145
|
+
};
|
|
1146
|
+
function isUnconditionedAllow(grant, now) {
|
|
1147
|
+
return grant.where === void 0 && grant.check === void 0 && grant.closure === void 0 && grant.portable && grant.purpose === void 0 && grant.viaOnly === void 0 && grant.requires === void 0 && isActive(grant.validity, now);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* The row condition a grant's `requires` adds: the row's instance of some
|
|
1151
|
+
* scope is one where the subject holds `key` through a role grant without a
|
|
1152
|
+
* row condition, minus the instances a deny of `key` reaches at that scope.
|
|
1153
|
+
* `undefined` when the subject holds `key` globally that way, so every row
|
|
1154
|
+
* passes. The in-process mirror of the SQL `permdock_has_permission` and
|
|
1155
|
+
* `permitted_<scope>_ids_by_permission`.
|
|
1156
|
+
*/
|
|
1157
|
+
function requirementCondition(policy, subject, keys, resource, now, customRoles, customGrants) {
|
|
1158
|
+
let where;
|
|
1159
|
+
for (const key of keys) where = combineWhere(where, keyRequirement(policy, subject, key, resource, now, customRoles, customGrants));
|
|
1160
|
+
return where;
|
|
1161
|
+
}
|
|
1162
|
+
function keyRequirement(policy, subject, key, resource, now, customRoles, customGrants) {
|
|
1163
|
+
const principal = subject.principal;
|
|
1164
|
+
if (principal === null) return NEVER;
|
|
1165
|
+
const scopes = scopeList(policy.scopes);
|
|
1166
|
+
const declared = declaredRoleNames(policy);
|
|
1167
|
+
const global = new Set(expandRoleNames(principal.roles ?? [], declared, customRoles, principal.tenant).roles);
|
|
1168
|
+
const held = (principal.memberships ?? []).filter((membership) => !isMembershipExpired(membership, now) && keepsPermission(membership, key)).map((membership) => ({
|
|
1169
|
+
membership,
|
|
1170
|
+
roles: new Set(expandRoleNames(membership.roles, declared, customRoles, tenantOf(membership, scopes)).roles)
|
|
1171
|
+
}));
|
|
1172
|
+
const allowed = /* @__PURE__ */ new Map();
|
|
1173
|
+
const denied = /* @__PURE__ */ new Map();
|
|
1174
|
+
let globalAllow = false;
|
|
1175
|
+
let globalDeny = false;
|
|
1176
|
+
const note = (effect, membership) => {
|
|
1177
|
+
if (membership === void 0) {
|
|
1178
|
+
if (effect === "allow") globalAllow = true;
|
|
1179
|
+
else globalDeny = true;
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
if (membership.scope === void 0 || membership.id === void 0) return;
|
|
1183
|
+
const target = effect === "allow" ? allowed : denied;
|
|
1184
|
+
const ids = target.get(membership.scope) ?? /* @__PURE__ */ new Set();
|
|
1185
|
+
ids.add(membership.id);
|
|
1186
|
+
target.set(membership.scope, ids);
|
|
1187
|
+
};
|
|
1188
|
+
const applies = (grant) => grant.effect === "allow" ? isUnconditionedAllow(grant, now) : isActive(grant.validity, now);
|
|
1189
|
+
for (const grant of policy.index.grantsByKey.get(key) ?? []) {
|
|
1190
|
+
const items = flattenGrantee(grant.to);
|
|
1191
|
+
if (grant.breakGlass !== void 0 || !applies(grant) || items.length === 0 || !items.every((item) => item.kind === "role")) continue;
|
|
1192
|
+
const roles = items.flatMap((item) => item.kind === "role" ? [item] : []);
|
|
1193
|
+
if (!roles.every((item) => item.scope !== "global" || global.has(item.role))) continue;
|
|
1194
|
+
const scoped = roles.filter((item) => item.scope !== "global");
|
|
1195
|
+
if (scoped.length === 0) {
|
|
1196
|
+
note(grant.effect);
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
for (const entry of held) if (scoped.every((item) => typeof item.scope === "string" && entry.membership.scope === item.scope && entry.roles.has(item.role))) note(grant.effect, entry.membership);
|
|
1200
|
+
}
|
|
1201
|
+
for (const { grant, role } of customGrants) {
|
|
1202
|
+
if (grant.permission.key !== key || !applies(grant)) continue;
|
|
1203
|
+
if (holdsGlobalCustomRole(principal.roles, role)) note(grant.effect);
|
|
1204
|
+
for (const entry of held) if (holdsCustomRole(entry.membership, role, scopes)) note(grant.effect, entry.membership);
|
|
1205
|
+
}
|
|
1206
|
+
if (globalAllow && !globalDeny) return;
|
|
1207
|
+
const parts = [];
|
|
1208
|
+
for (const name of resource === void 0 ? [] : partitionsOf(resource, scopes)) {
|
|
1209
|
+
const field = scopeField(resource, name, scopes);
|
|
1210
|
+
const deniedIds = denied.get(name);
|
|
1211
|
+
const ids = [...allowed.get(name) ?? []].filter((id) => deniedIds?.has(id) !== true).toSorted();
|
|
1212
|
+
if (field !== void 0 && ids.length > 0) parts.push({
|
|
1213
|
+
op: "in",
|
|
1214
|
+
field,
|
|
1215
|
+
value: ids
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
return sole(parts) ?? {
|
|
1219
|
+
op: "or",
|
|
1220
|
+
conditions: parts
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
992
1223
|
function alternativesFor(policy, permission, subject, now, env) {
|
|
993
1224
|
return listPermissions(policy.permissions).filter((leaf) => leaf.resource === permission.resource && leaf.key !== permission.key).filter((leaf) => {
|
|
994
1225
|
return evaluate(policy, subject, leaf, void 0, {
|
|
@@ -998,11 +1229,35 @@ function alternativesFor(policy, permission, subject, now, env) {
|
|
|
998
1229
|
}, {
|
|
999
1230
|
...env,
|
|
1000
1231
|
emit: false,
|
|
1001
|
-
skipAlternatives: true
|
|
1232
|
+
skipAlternatives: true,
|
|
1233
|
+
outcomeOnly: true
|
|
1002
1234
|
}).outcome === "granted";
|
|
1003
1235
|
});
|
|
1004
1236
|
}
|
|
1005
|
-
function
|
|
1237
|
+
function inheritedVerdict(policy, subject, resource, id, key, now, env) {
|
|
1238
|
+
const leaf = findPermission(policy.permissions, key);
|
|
1239
|
+
const loaded = env.relations?.row({
|
|
1240
|
+
resource,
|
|
1241
|
+
id
|
|
1242
|
+
});
|
|
1243
|
+
if (leaf === void 0 || loaded === void 0) return "relation-unavailable";
|
|
1244
|
+
if (loaded === "pending" || loaded === "failed") return "relation-unavailable";
|
|
1245
|
+
if (loaded.row === null) return false;
|
|
1246
|
+
const decision = evaluate(policy, subject, leaf, loaded.row, {
|
|
1247
|
+
trusted: true,
|
|
1248
|
+
source: "simulate",
|
|
1249
|
+
now
|
|
1250
|
+
}, {
|
|
1251
|
+
...env,
|
|
1252
|
+
emit: false,
|
|
1253
|
+
skipAlternatives: true,
|
|
1254
|
+
outcomeOnly: true
|
|
1255
|
+
});
|
|
1256
|
+
if (decision.outcome === "granted") return true;
|
|
1257
|
+
const reasons = decision.outcome === "denied" ? decision.denials.map((denial) => denial.reason) : [];
|
|
1258
|
+
return reasons.includes("relation-unavailable") ? "relation-unavailable" : reasons.includes("relation-depth") ? "relation-depth" : false;
|
|
1259
|
+
}
|
|
1260
|
+
function evaluateGrantCondition(grant, permission, current, next, subject, now, scopes, relations, permitted) {
|
|
1006
1261
|
let unknown;
|
|
1007
1262
|
const onOpaque = () => {
|
|
1008
1263
|
unknown ??= "opaque-condition";
|
|
@@ -1012,7 +1267,7 @@ function evaluateGrantCondition(grant, permission, current, next, subject, now,
|
|
|
1012
1267
|
unknown ??= "relation-unavailable";
|
|
1013
1268
|
return false;
|
|
1014
1269
|
}
|
|
1015
|
-
const verdict = resolveRelated(condition, row, subject, now, relations);
|
|
1270
|
+
const verdict = resolveRelated(condition, row, subject, now, relations, permitted);
|
|
1016
1271
|
if (verdict === "relation-depth" || verdict === "relation-unavailable") {
|
|
1017
1272
|
unknown ??= verdict;
|
|
1018
1273
|
return false;
|
|
@@ -1140,12 +1395,12 @@ function traceOf(tracer) {
|
|
|
1140
1395
|
skipped: tracer.skipped
|
|
1141
1396
|
};
|
|
1142
1397
|
}
|
|
1143
|
-
function evaluate(policy,
|
|
1398
|
+
function evaluate(policy, given, permission, data, options, env) {
|
|
1399
|
+
const subject = subjectForPermission(given, permission.key);
|
|
1144
1400
|
const now = options.now ?? nowSeconds();
|
|
1145
1401
|
const trusted = options.trusted === true;
|
|
1146
1402
|
const resource = getResource(policy.permissions, permission.resource);
|
|
1147
|
-
let current = data;
|
|
1148
|
-
let next = data;
|
|
1403
|
+
let { current, next } = rowValues(permission, data);
|
|
1149
1404
|
const tracer = options.explain === true ? {
|
|
1150
1405
|
evaluated: 0,
|
|
1151
1406
|
allows: [],
|
|
@@ -1153,21 +1408,13 @@ function evaluate(policy, subject, permission, data, options, env) {
|
|
|
1153
1408
|
skipped: []
|
|
1154
1409
|
} : void 0;
|
|
1155
1410
|
const complete = (decision, membership) => {
|
|
1156
|
-
const final = freezeDeep(tracer === void 0 ? decision : {
|
|
1411
|
+
const final = env.outcomeOnly === true ? decision : freezeDeep(tracer === void 0 ? decision : {
|
|
1157
1412
|
...decision,
|
|
1158
1413
|
trace: traceOf(tracer)
|
|
1159
1414
|
});
|
|
1160
|
-
finish(policy,
|
|
1415
|
+
finish(policy, given, permission, current, final, options, env, trusted, membership);
|
|
1161
1416
|
return final;
|
|
1162
1417
|
};
|
|
1163
|
-
if (permission.kind === "instance" && isRowPair(data)) {
|
|
1164
|
-
current = data.current;
|
|
1165
|
-
next = data.next;
|
|
1166
|
-
}
|
|
1167
|
-
if (permission.kind === "collection") {
|
|
1168
|
-
current = void 0;
|
|
1169
|
-
next = data;
|
|
1170
|
-
}
|
|
1171
1418
|
try {
|
|
1172
1419
|
if (data !== void 0 || permission.kind === "instance" && policy.validate === "always") {
|
|
1173
1420
|
const validated = validateBoundary(permission, resource, permission.kind === "instance" && isRowPair(data) ? data.current : data, policy.validate, trusted, options.boundary ?? "manual");
|
|
@@ -1323,7 +1570,7 @@ function evaluate(policy, subject, permission, data, options, env) {
|
|
|
1323
1570
|
const matchRow = (row) => matchScopedMembership(subject, roleItem.scope, roleItem.role, row, scopes, resource, policy.resources, now, (membership) => {
|
|
1324
1571
|
if (custom === void 0) return expandRoleNames(membership.roles, declared, env.customRoles, tenantOf(membership, scopes)).roles;
|
|
1325
1572
|
return holdsCustomRole(membership, custom, scopes) ? [custom.name] : [];
|
|
1326
|
-
}, env.team, walkRole);
|
|
1573
|
+
}, env.team, walkRole, permission.kind === "instance", options.scope);
|
|
1327
1574
|
const scopeMatch = matchWriteScope(matchRow, permission.kind === "instance", current, next);
|
|
1328
1575
|
if (!scopeMatch.ok) {
|
|
1329
1576
|
denials.push({
|
|
@@ -1347,11 +1594,12 @@ function evaluate(policy, subject, permission, data, options, env) {
|
|
|
1347
1594
|
else skip(tracer, grant, permission.key, "validity");
|
|
1348
1595
|
continue;
|
|
1349
1596
|
}
|
|
1597
|
+
const requirement = grant.requires === void 0 || grant.effect !== "allow" ? void 0 : requirementCondition(policy, subject, grant.requires, resource, now, env.customRoles, env.customGrants);
|
|
1350
1598
|
const merged = freezeDeep(compact({
|
|
1351
1599
|
...grant,
|
|
1352
|
-
where: combineWhere(grant.where, granteeMatch.where)
|
|
1600
|
+
where: combineWhere(combineWhere(grant.where, granteeMatch.where), requirement)
|
|
1353
1601
|
}));
|
|
1354
|
-
const condition = evaluateGrantCondition(merged, permission, current, next, subject, now, scopes, env.relations);
|
|
1602
|
+
const condition = evaluateGrantCondition(merged, permission, current, next, subject, now, scopes, env.relations, (target, id, key) => inheritedVerdict(policy, given, target, id, key, now, env));
|
|
1355
1603
|
if (!condition.matched && grant.effect === "deny" && isUnevaluable(condition.reason) && grantCoversField(grant.fields, options.field, grant.effect)) {
|
|
1356
1604
|
if (condition.reason === "closure-error") emitSafe(env.listeners.error, condition.cause ?? /* @__PURE__ */ new Error("closure-error"), env.listeners);
|
|
1357
1605
|
tracer?.denies.push(matchedOf(merged, permission.key));
|
|
@@ -1442,7 +1690,12 @@ function evaluate(policy, subject, permission, data, options, env) {
|
|
|
1442
1690
|
}],
|
|
1443
1691
|
alternatives: env.skipAlternatives ? [] : alternativesFor(policy, permission, subject, now, env)
|
|
1444
1692
|
});
|
|
1445
|
-
const
|
|
1693
|
+
const unscoped = subject.actor !== void 0 && ceiling === void 0;
|
|
1694
|
+
let delegationMiss = coveredByDelegation(permission, subject.delegation, resourceIdOf(current), unscoped);
|
|
1695
|
+
const requirementsCovered = allows.filter((candidate) => coversRequirements(policy.permissions, candidate.grant.requires, subject.delegation, resourceIdOf(current), unscoped));
|
|
1696
|
+
let usable = delegationMiss === void 0 ? requirementsCovered : requirementsCovered.filter((candidate) => requiresStandIn(candidate.grant));
|
|
1697
|
+
if (delegationMiss === "not-delegated" && usable.length > 0) delegationMiss = void 0;
|
|
1698
|
+
if (delegationMiss === void 0 && usable.length === 0) delegationMiss = "not-delegated";
|
|
1446
1699
|
if (delegationMiss !== void 0) return complete({
|
|
1447
1700
|
outcome: "denied",
|
|
1448
1701
|
denials: [{
|
|
@@ -1452,12 +1705,12 @@ function evaluate(policy, subject, permission, data, options, env) {
|
|
|
1452
1705
|
alternatives: env.skipAlternatives ? [] : alternativesFor(policy, permission, subject, now, env)
|
|
1453
1706
|
});
|
|
1454
1707
|
const approvalPolicies = env.approvalPolicies;
|
|
1455
|
-
if (approvalPolicies === "failed" &&
|
|
1708
|
+
if (approvalPolicies === "failed" && usable.length > 0) return complete({
|
|
1456
1709
|
outcome: "denied",
|
|
1457
1710
|
denials: [{
|
|
1458
1711
|
role: null,
|
|
1459
1712
|
reason: "approval",
|
|
1460
|
-
detail:
|
|
1713
|
+
detail: APPROVAL_POLICY_UNAVAILABLE
|
|
1461
1714
|
}],
|
|
1462
1715
|
alternatives: []
|
|
1463
1716
|
});
|
|
@@ -1474,7 +1727,7 @@ function evaluate(policy, subject, permission, data, options, env) {
|
|
|
1474
1727
|
let matchedAllow;
|
|
1475
1728
|
let approval;
|
|
1476
1729
|
let quotaState = {};
|
|
1477
|
-
for (const candidate of
|
|
1730
|
+
for (const candidate of usable) {
|
|
1478
1731
|
approval = approvalOf(candidate);
|
|
1479
1732
|
const consume = !requiresApproval(approval) && shouldConsumeQuota(options.source, env.simulated);
|
|
1480
1733
|
const quota = applyQuota({
|
|
@@ -1509,9 +1762,9 @@ function evaluate(policy, subject, permission, data, options, env) {
|
|
|
1509
1762
|
denials: quotaDenials.length > 0 ? quotaDenials : denials,
|
|
1510
1763
|
alternatives: env.skipAlternatives ? [] : alternativesFor(policy, permission, subject, now, env)
|
|
1511
1764
|
});
|
|
1512
|
-
const resourceId = permission.kind === "collection" ? "*" : current
|
|
1765
|
+
const resourceId = permission.kind === "collection" ? "*" : rowIdOf(current, resource?.id);
|
|
1513
1766
|
const version = approval !== void 0 && approval !== "human" && approval.staleOn === "resource-change" && resource?.version !== void 0 ? versionOf(current, resource.version) : void 0;
|
|
1514
|
-
const token = env.simulated ? "pd1.simulated" : decisionToken({
|
|
1767
|
+
const token = env.simulated ? "pd1.simulated" : env.outcomeOnly === true ? "pd1.outcome" : decisionToken({
|
|
1515
1768
|
key: permission.key,
|
|
1516
1769
|
resourceId,
|
|
1517
1770
|
principal: subject.principal,
|
|
@@ -1647,7 +1900,7 @@ function audiencesOf(policy, names) {
|
|
|
1647
1900
|
/** Whether `membership` is the instance itself or one of its ancestors named in `within`. */
|
|
1648
1901
|
function coversInstance(membership, scopes, scope, id, within) {
|
|
1649
1902
|
if (membership.scope === void 0 || membership.id === void 0) return false;
|
|
1650
|
-
if (membership.scope === scope) return membership.id
|
|
1903
|
+
if (membership.scope === scope) return sameId(membership.id, id);
|
|
1651
1904
|
if (!scopeChain(scopes, scope).slice(1).includes(membership.scope)) return false;
|
|
1652
1905
|
const wanted = within === void 0 ? void 0 : Object.entries(within).find(([key]) => resolveScope(scopes, key) === membership.scope)?.[1] ?? void 0;
|
|
1653
1906
|
return wanted !== void 0 && scopeIdOf(membership, membership.scope) === wanted;
|
|
@@ -1661,7 +1914,22 @@ function rolesAtInstance(policy, principal, scopes, scope, id, within, now) {
|
|
|
1661
1914
|
}
|
|
1662
1915
|
/** Whether the principal holds `role` on a live membership of exactly this instance. */
|
|
1663
1916
|
function holdsAt(principal, role, scope, id, now) {
|
|
1664
|
-
return (principal.memberships ?? []).some((membership) => membership.scope === scope && membership.id
|
|
1917
|
+
return (principal.memberships ?? []).some((membership) => membership.scope === scope && sameId(membership.id, id) && !isMembershipExpired(membership, now) && membership.roles.includes(role));
|
|
1918
|
+
}
|
|
1919
|
+
function includedRoles(role) {
|
|
1920
|
+
return isReadonlyArray(role.includes) ? role.includes.filter((name) => typeof name === "string") : [];
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* The membership kinds a custom role may be held through: those every
|
|
1924
|
+
* included declared role with `for` allows. `undefined` when none restricts.
|
|
1925
|
+
*/
|
|
1926
|
+
function customForKinds(policy, role) {
|
|
1927
|
+
let kinds;
|
|
1928
|
+
for (const name of includedRoles(role)) {
|
|
1929
|
+
const listed = bindingOf(policy, name)?.for;
|
|
1930
|
+
if (listed !== void 0) kinds = kinds === void 0 ? listed : kinds.filter((kind) => listed.includes(kind));
|
|
1931
|
+
}
|
|
1932
|
+
return kinds;
|
|
1665
1933
|
}
|
|
1666
1934
|
function conflictsWith(policy, role) {
|
|
1667
1935
|
const out = new Set(bindingOf(policy, role)?.exclusiveWith ?? []);
|
|
@@ -1707,7 +1975,7 @@ function heldAnchor(principal, scopes, scope, id, now) {
|
|
|
1707
1975
|
* The one rule check behind `permdock.decideRoleChange`. The actor is always
|
|
1708
1976
|
* the instance's own principal; nothing in `change` can stand in for it.
|
|
1709
1977
|
*/
|
|
1710
|
-
function decideRoleChange(policy, principal, scopes, change, authority, now, options = {}) {
|
|
1978
|
+
function decideRoleChange(policy, principal, scopes, change, customRoles, authority, now, options = {}) {
|
|
1711
1979
|
const name = typeof change?.role === "string" ? change.role : change?.role?.key;
|
|
1712
1980
|
const denials = [];
|
|
1713
1981
|
const deny = (reason, detail) => {
|
|
@@ -1737,12 +2005,15 @@ function decideRoleChange(policy, principal, scopes, change, authority, now, opt
|
|
|
1737
2005
|
return done(null);
|
|
1738
2006
|
}
|
|
1739
2007
|
const binding = bindingOf(policy, name);
|
|
1740
|
-
|
|
2008
|
+
const declared = declaredRoleNames$1(policy).has(name);
|
|
2009
|
+
const named = declared ? [] : tenantCustomRoles(policy, customRoles).filter((role) => role.name === name);
|
|
2010
|
+
if (!declared && named.length === 0) {
|
|
1741
2011
|
deny("unknown-role");
|
|
1742
2012
|
return done(null);
|
|
1743
2013
|
}
|
|
1744
2014
|
const scope = resolveScope(scopes, change.scope);
|
|
1745
|
-
const
|
|
2015
|
+
const customAt = named.map((role) => customRoleScope(role, scopes));
|
|
2016
|
+
const on = declared ? binding?.on ?? findRole(policy.vocabulary?.roles, name)?.on : customAt.includes(scope) ? scope : customAt[0];
|
|
1746
2017
|
const heldAt = typeof on === "string" ? resolveScope(scopes, on) : void 0;
|
|
1747
2018
|
if (scope === void 0 || heldAt === void 0 || heldAt !== scope) {
|
|
1748
2019
|
deny("scope", { expected: heldAt ?? null });
|
|
@@ -1768,6 +2039,11 @@ function decideRoleChange(policy, principal, scopes, change, authority, now, opt
|
|
|
1768
2039
|
});
|
|
1769
2040
|
return done(null);
|
|
1770
2041
|
}
|
|
2042
|
+
const custom = named.find((role) => sameId(role.tenant, tenant) && customRoleScope(role, scopes) === scope && sameId(customRoleId(role) ?? change.id, change.id));
|
|
2043
|
+
if (!declared && custom === void 0) {
|
|
2044
|
+
deny("unknown-role");
|
|
2045
|
+
return done(null);
|
|
2046
|
+
}
|
|
1771
2047
|
if (target.managedBy === "idp") {
|
|
1772
2048
|
deny("externally-managed");
|
|
1773
2049
|
return done(null);
|
|
@@ -1783,19 +2059,21 @@ function decideRoleChange(policy, principal, scopes, change, authority, now, opt
|
|
|
1783
2059
|
} else {
|
|
1784
2060
|
if (self) deny(kind === "revoke" ? "self-demotion" : "not-assignable-by", { self: true });
|
|
1785
2061
|
const allowed = authority(tenant);
|
|
1786
|
-
|
|
1787
|
-
|
|
2062
|
+
const revocable = kind === "revoke" && custom !== void 0 && allowed.inert?.has(name);
|
|
2063
|
+
if (!allowed.assignable.has(name) && revocable !== true) deny("not-assignable-by");
|
|
2064
|
+
else if (custom === void 0 && usesAssigns(policy) && !allowed.manage) {
|
|
1788
2065
|
const held = rolesAtInstance(policy, principal, scopes, scope, change.id, within, now);
|
|
1789
2066
|
by = held.find((role) => bindingOf(policy, role)?.assigns?.includes(name) === true) ?? null;
|
|
1790
2067
|
if (by === null) deny("not-assignable-by", { held });
|
|
1791
2068
|
}
|
|
1792
2069
|
}
|
|
1793
2070
|
if (kind !== "revoke" && !already) {
|
|
1794
|
-
|
|
2071
|
+
const kinds = custom === void 0 ? binding?.for : customForKinds(policy, custom);
|
|
2072
|
+
if (kinds !== void 0 && (target.via === void 0 || !kinds.includes(target.via))) deny("not-allowed-for-membership", {
|
|
1795
2073
|
via: target.via ?? null,
|
|
1796
|
-
for:
|
|
2074
|
+
for: kinds
|
|
1797
2075
|
});
|
|
1798
|
-
const conflicts = conflictsWith(policy, name);
|
|
2076
|
+
const conflicts = custom === void 0 ? conflictsWith(policy, name) : new Set(includedRoles(custom).flatMap((include) => Array.from(conflictsWith(policy, include))));
|
|
1799
2077
|
const clash = targetRoles.filter((role) => conflicts.has(role));
|
|
1800
2078
|
if (clash.length > 0) deny("conflicting-role", { with: clash });
|
|
1801
2079
|
}
|
|
@@ -1849,7 +2127,7 @@ async function discoverRole(item, allow, ctx) {
|
|
|
1849
2127
|
ctx.incomplete();
|
|
1850
2128
|
return;
|
|
1851
2129
|
}
|
|
1852
|
-
const key =
|
|
2130
|
+
const key = scopeField(ctx.resource, item.scope, ctx.scopes);
|
|
1853
2131
|
const id = key === void 0 ? void 0 : relationId(ownGet(ctx.row, key));
|
|
1854
2132
|
if (id === void 0) return;
|
|
1855
2133
|
const members = await ctx.list(item.scope, id);
|
|
@@ -2002,6 +2280,7 @@ function discover(grant, item, ctx) {
|
|
|
2002
2280
|
case "plan":
|
|
2003
2281
|
case "actor":
|
|
2004
2282
|
case "assurance":
|
|
2283
|
+
case "inherit":
|
|
2005
2284
|
ctx.incomplete();
|
|
2006
2285
|
return Promise.resolve();
|
|
2007
2286
|
default: return unknownKind(item, ctx);
|
|
@@ -2069,7 +2348,7 @@ async function whoCan(input) {
|
|
|
2069
2348
|
};
|
|
2070
2349
|
await Promise.all((policy.index.grantsByKey.get(permission.key) ?? []).flatMap((grant) => flattenGrantee(grant.to).map((item) => discover(grant, item, ctx))));
|
|
2071
2350
|
const root = rootScope(scopes);
|
|
2072
|
-
const tenantKey = root === void 0 ? void 0 :
|
|
2351
|
+
const tenantKey = root === void 0 ? void 0 : scopeField(getResource(policy.permissions, permission.resource), root, scopes);
|
|
2073
2352
|
const tenant = tenantKey === void 0 ? void 0 : relationId(ownGet(row, tenantKey));
|
|
2074
2353
|
const env = {
|
|
2075
2354
|
emit: false,
|
|
@@ -2161,8 +2440,8 @@ function heldRoleNames(policy, subject, tenant, only, now) {
|
|
|
2161
2440
|
if (only?.scope !== void 0 && scope === void 0) return [];
|
|
2162
2441
|
const names = new Set(scope === void 0 ? subject.principal.roles ?? [] : []);
|
|
2163
2442
|
for (const membership of subject.principal.memberships ?? []) {
|
|
2164
|
-
if (isMembershipExpired(membership, now)) continue;
|
|
2165
|
-
if (!(scope === void 0 ? tenantOf(membership, scopes) === tenant : membership.scope === scope && (only?.id === void 0 || membership.id
|
|
2443
|
+
if (isMembershipExpired(membership, now) || membership.keep !== void 0) continue;
|
|
2444
|
+
if (!(scope === void 0 ? tenantOf(membership, scopes) === tenant : membership.scope === scope && (only?.id === void 0 || sameId(membership.id, only.id)))) continue;
|
|
2166
2445
|
for (const role of membership.roles) names.add(role);
|
|
2167
2446
|
}
|
|
2168
2447
|
return rankRoles(policy, [...names]);
|
|
@@ -2183,11 +2462,12 @@ function roleLeaf(policy, name) {
|
|
|
2183
2462
|
function graphAware(grant, where) {
|
|
2184
2463
|
const combined = combineWhere(grant.where, where);
|
|
2185
2464
|
const graph = hasConditionOp(combined, "related");
|
|
2465
|
+
const inherits = flattenGrantee(grant.to).some((item) => item.kind === "inherit");
|
|
2186
2466
|
return freezeDeep(compact({
|
|
2187
2467
|
...grant,
|
|
2188
2468
|
where: combined,
|
|
2189
2469
|
portable: graph ? false : grant.portable,
|
|
2190
|
-
graph: graph && grant.portable ? true : void 0
|
|
2470
|
+
graph: graph && grant.portable && !inherits ? true : void 0
|
|
2191
2471
|
}));
|
|
2192
2472
|
}
|
|
2193
2473
|
/**
|
|
@@ -2215,33 +2495,35 @@ function collectSnapshotGrants(policy, subject, customRoles, now = nowSeconds(),
|
|
|
2215
2495
|
roles: new Set(expandRoleNames(membership.roles, declared, customRoles, tenantOf(membership, scopes)).roles)
|
|
2216
2496
|
}));
|
|
2217
2497
|
const holds = (entry, item) => entry.roles.has(item.role) && (typeof item.scope === "string" ? entry.membership.scope === item.scope : entry.membership.on !== void 0);
|
|
2498
|
+
const requirementOf = (grant, resource) => grant.requires === void 0 || grant.effect !== "allow" ? void 0 : requirementCondition(policy, subject, grant.requires, resource, now, customRoles, customGrants);
|
|
2218
2499
|
const out = [];
|
|
2219
2500
|
for (const grant of grantList(policy)) {
|
|
2501
|
+
if (mode === "held" && !coversRequirements(policy.permissions, grant.requires, subject.delegation)) continue;
|
|
2220
2502
|
const resource = getResource(policy.permissions, grant.permission.resource);
|
|
2221
2503
|
const match = matchGrantee(grant.to, subject, now, resource, scopes, policy.resources);
|
|
2222
2504
|
if (skip(grant, match)) continue;
|
|
2223
2505
|
const roleItems = flattenGrantee(grant.to).filter((item) => item.kind === "role");
|
|
2224
2506
|
if (roleItems.length > 0 && subject.principal === null) continue;
|
|
2225
2507
|
if (!roleItems.every((item) => item.scope !== "global" || global.roles.includes(item.role))) continue;
|
|
2226
|
-
const merged = graphAware(grant, match.where);
|
|
2508
|
+
const merged = graphAware(grant, combineWhere(match.where, requirementOf(grant, resource)));
|
|
2227
2509
|
const scoped = roleItems.filter((item) => item.scope !== "global");
|
|
2228
2510
|
if (scoped.length === 0) {
|
|
2229
2511
|
out.push({ grant: merged });
|
|
2230
2512
|
continue;
|
|
2231
2513
|
}
|
|
2232
|
-
for (const entry of held) if (scoped.every((item) => item.kind === "role" && holds(entry, item))) out.push({
|
|
2514
|
+
for (const entry of held) if (keepsPermission(entry.membership, grant.permission.key) && scoped.every((item) => item.kind === "role" && holds(entry, item))) out.push({
|
|
2233
2515
|
grant: merged,
|
|
2234
2516
|
membership: entry.membership
|
|
2235
2517
|
});
|
|
2236
2518
|
}
|
|
2237
2519
|
for (const { grant, role } of customGrants) {
|
|
2238
2520
|
const globalHeld = holdsGlobalCustomRole(subject.principal?.roles, role);
|
|
2239
|
-
const holders = held.filter((entry) => holdsCustomRole(entry.membership, role, scopes));
|
|
2521
|
+
const holders = held.filter((entry) => keepsPermission(entry.membership, grant.permission.key) && holdsCustomRole(entry.membership, role, scopes));
|
|
2240
2522
|
if (holders.length === 0 && !globalHeld) continue;
|
|
2241
2523
|
const resource = getResource(policy.permissions, grant.permission.resource);
|
|
2242
2524
|
const match = matchGrantee(grant.to, subject, now, resource, scopes, policy.resources);
|
|
2243
2525
|
if (skip(grant, match)) continue;
|
|
2244
|
-
const merged = graphAware(grant, match.where);
|
|
2526
|
+
const merged = graphAware(grant, combineWhere(match.where, requirementOf(grant, resource)));
|
|
2245
2527
|
if (globalHeld) out.push({ grant: merged });
|
|
2246
2528
|
for (const entry of holders) out.push({
|
|
2247
2529
|
grant: merged,
|
|
@@ -2262,6 +2544,20 @@ function assignableCandidates(policy) {
|
|
|
2262
2544
|
return rankRoles(policy, [...new Set(names)]).map((name) => roleLeaf(policy, name));
|
|
2263
2545
|
}
|
|
2264
2546
|
/**
|
|
2547
|
+
* The levels a held allow of a permission covers: its own `level`, every
|
|
2548
|
+
* level when it has no row condition, or the levels whose condition equals
|
|
2549
|
+
* its condition.
|
|
2550
|
+
*/
|
|
2551
|
+
function heldLevels(policy, grant, names) {
|
|
2552
|
+
if (grant.level !== void 0) return [grant.level];
|
|
2553
|
+
if (grant.where === void 0) return names;
|
|
2554
|
+
const held = canonicalJson(grant.where);
|
|
2555
|
+
return names.filter((name) => {
|
|
2556
|
+
const condition = levelCondition(policy, grant.permission.resource, name);
|
|
2557
|
+
return condition !== void 0 && canonicalJson(condition) === held;
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
/**
|
|
2265
2561
|
* What the subject may hand out in `tenant`: declared assignable roles and the
|
|
2266
2562
|
* tenant custom-role ceiling, narrowed by `allowed` (`RoleSource.assignable`),
|
|
2267
2563
|
* then intersected with the permissions the subject holds there. Holding a
|
|
@@ -2272,7 +2568,9 @@ function assignableIn(policy, subject, customRoles, customGrants, tenant, allowe
|
|
|
2272
2568
|
if (principal === null) return {
|
|
2273
2569
|
roles: [],
|
|
2274
2570
|
permissions: [],
|
|
2275
|
-
|
|
2571
|
+
levels: {},
|
|
2572
|
+
manage: false,
|
|
2573
|
+
inert: []
|
|
2276
2574
|
};
|
|
2277
2575
|
const scopes = scopeList(policy.scopes);
|
|
2278
2576
|
const scoped = tenant === void 0 ? subject : freezeDeep(compact({
|
|
@@ -2282,7 +2580,8 @@ function assignableIn(policy, subject, customRoles, customGrants, tenant, allowe
|
|
|
2282
2580
|
tenant: resolveActiveTenant(principal, tenant, scopes, now)
|
|
2283
2581
|
})
|
|
2284
2582
|
}));
|
|
2285
|
-
const
|
|
2583
|
+
const heldGrants = collectSnapshotGrants(policy, scoped, customRoles, now, customGrants).filter((item) => item.grant.effect === "allow" && (item.membership === void 0 || !global && tenantOf(item.membership, scopes) === tenant && !isMembershipExpired(item.membership, now))).map((item) => item.grant);
|
|
2584
|
+
const heldKeys = new Set(heldGrants.map((grant) => grant.permission.key));
|
|
2286
2585
|
const heldNames = heldRoleNames(policy, subject, tenant, void 0, now);
|
|
2287
2586
|
const quiet = {
|
|
2288
2587
|
emit: false,
|
|
@@ -2313,10 +2612,72 @@ function assignableIn(policy, subject, customRoles, customGrants, tenant, allowe
|
|
|
2313
2612
|
});
|
|
2314
2613
|
const root = global ? "global" : rootScope(scopes);
|
|
2315
2614
|
const ceiling = new Set((root === void 0 ? [] : ceilingGrants(policy, root, allowed)).map((grant) => grant.permission.key));
|
|
2615
|
+
const permissions = listPermissions(policy.permissions).filter((leaf) => ceiling.has(leaf.key) && (manage || heldKeys.has(leaf.key)));
|
|
2616
|
+
const offeredLevels = (key, names) => {
|
|
2617
|
+
if (manage) return names;
|
|
2618
|
+
const reached = new Set(heldGrants.filter((grant) => grant.permission.key === key).flatMap((grant) => heldLevels(policy, grant, names)));
|
|
2619
|
+
return names.filter((name) => reached.has(name));
|
|
2620
|
+
};
|
|
2621
|
+
const levels = {};
|
|
2622
|
+
for (const leaf of permissions) {
|
|
2623
|
+
const names = leaf.kind === "instance" ? levelNames(policy, leaf.resource) : [];
|
|
2624
|
+
if (names.length > 0) levels[leaf.key] = offeredLevels(leaf.key, names);
|
|
2625
|
+
}
|
|
2626
|
+
const ceilingKeys = /* @__PURE__ */ new Map();
|
|
2627
|
+
const mayHandOut = (scope, grant) => {
|
|
2628
|
+
const key = grant.permission.key;
|
|
2629
|
+
let keys = ceilingKeys.get(scope);
|
|
2630
|
+
if (keys === void 0) {
|
|
2631
|
+
keys = new Set(ceilingGrants(policy, scope, allowed).map((item) => item.permission.key));
|
|
2632
|
+
ceilingKeys.set(scope, keys);
|
|
2633
|
+
}
|
|
2634
|
+
if (!keys.has(key) || !(manage || heldKeys.has(key))) return false;
|
|
2635
|
+
const names = grant.permission.kind === "instance" ? levelNames(policy, grant.permission.resource) : [];
|
|
2636
|
+
if (names.length === 0) return true;
|
|
2637
|
+
const offered = offeredLevels(key, names);
|
|
2638
|
+
return heldLevels(policy, grant, names).every((name) => offered.includes(name));
|
|
2639
|
+
};
|
|
2640
|
+
const custom = global || tenant === void 0 ? {
|
|
2641
|
+
roles: [],
|
|
2642
|
+
inert: []
|
|
2643
|
+
} : assignableCustomRoles(policy, customRoles, tenant, manage ? void 0 : roles, mayHandOut);
|
|
2316
2644
|
return {
|
|
2317
|
-
roles,
|
|
2318
|
-
permissions
|
|
2319
|
-
|
|
2645
|
+
roles: [...roles, ...custom.roles],
|
|
2646
|
+
permissions,
|
|
2647
|
+
levels,
|
|
2648
|
+
manage,
|
|
2649
|
+
inert: custom.inert
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
/**
|
|
2653
|
+
* The tenant's custom roles the subject may hand out: held at a scope it may
|
|
2654
|
+
* assign declared roles at (any scope with `meta.manageRoles`), allowing at
|
|
2655
|
+
* least one permission after the ceiling, and only permissions and levels it
|
|
2656
|
+
* may hand out at that scope. A role that allows nothing is `inert`.
|
|
2657
|
+
*/
|
|
2658
|
+
function assignableCustomRoles(policy, customRoles, tenant, declared, mayHandOut) {
|
|
2659
|
+
const scopes = scopeList(policy.scopes);
|
|
2660
|
+
const root = rootScope(scopes);
|
|
2661
|
+
const assignsAt = declared === void 0 ? void 0 : new Set(declared.map((leaf) => leaf.on === void 0 ? root : resolveScope(scopes, leaf.on)));
|
|
2662
|
+
const out = /* @__PURE__ */ new Map();
|
|
2663
|
+
const inert = /* @__PURE__ */ new Set();
|
|
2664
|
+
for (const role of tenantCustomRoles(policy, customRoles)) {
|
|
2665
|
+
if (role.tenant !== tenant || out.has(role.name)) continue;
|
|
2666
|
+
const at = customRoleScope(role, scopes);
|
|
2667
|
+
if (at === void 0 || assignsAt !== void 0 && !assignsAt.has(at)) continue;
|
|
2668
|
+
const { grants } = resolveCustomRole(policy, role);
|
|
2669
|
+
if (!grants.some((grant) => grant.effect === "allow")) {
|
|
2670
|
+
inert.add(role.name);
|
|
2671
|
+
continue;
|
|
2672
|
+
}
|
|
2673
|
+
if (grants.every((grant) => grant.effect === "deny" || mayHandOut(at, grant))) out.set(role.name, synthesiseRole(role.name, {
|
|
2674
|
+
on: at,
|
|
2675
|
+
assignable: true
|
|
2676
|
+
}));
|
|
2677
|
+
}
|
|
2678
|
+
return {
|
|
2679
|
+
roles: [...out.values()].toSorted((a, b) => a.key.localeCompare(b.key)),
|
|
2680
|
+
inert: [...inert].filter((name) => !out.has(name)).toSorted()
|
|
2320
2681
|
};
|
|
2321
2682
|
}
|
|
2322
2683
|
/** The one path from a resolved subject to a `Snapshot`; pure apart from the default clock. */
|
|
@@ -2340,14 +2701,16 @@ function snapshotOf(policy, subject, options) {
|
|
|
2340
2701
|
now: Math.floor(now),
|
|
2341
2702
|
vocabulary: policy.vocabulary,
|
|
2342
2703
|
scopes: snapshotScopes(policy),
|
|
2704
|
+
idOf: (name) => getResource(policy.permissions, name)?.id,
|
|
2343
2705
|
delegated: delegatedPermissions(policy.delegations, subject, new Set(roles), now),
|
|
2344
2706
|
assignable: (tenant) => {
|
|
2345
2707
|
const found = assignableIn(policy, subject, options.customRoles, customGrants, tenant, options.assignable?.get(tenant), now);
|
|
2346
|
-
return {
|
|
2708
|
+
return compact({
|
|
2347
2709
|
tenant,
|
|
2348
2710
|
roles: found.roles.map((leaf) => leaf.key),
|
|
2349
|
-
permissions: found.permissions
|
|
2350
|
-
|
|
2711
|
+
permissions: found.permissions,
|
|
2712
|
+
levels: Object.keys(found.levels).length === 0 ? void 0 : found.levels
|
|
2713
|
+
});
|
|
2351
2714
|
}
|
|
2352
2715
|
}));
|
|
2353
2716
|
}
|
|
@@ -2356,12 +2719,18 @@ function snapshotScopes(policy) {
|
|
|
2356
2719
|
if (policy.scopes.length === 0) return;
|
|
2357
2720
|
const resources = [...policy.resources.values()];
|
|
2358
2721
|
return policy.scopes.map((scope) => {
|
|
2359
|
-
const
|
|
2722
|
+
const nodes = resources.filter((node) => partitionsOf(node, policy.scopes).includes(scope.name));
|
|
2723
|
+
const partitioned = nodes.map((node) => node.name);
|
|
2724
|
+
const fields = Object.fromEntries(nodes.flatMap((node) => {
|
|
2725
|
+
const field = scopeField(node, scope.name, policy.scopes);
|
|
2726
|
+
return field === void 0 || field === scope.key ? [] : [[node.name, field]];
|
|
2727
|
+
}));
|
|
2360
2728
|
return compact({
|
|
2361
2729
|
name: scope.name,
|
|
2362
2730
|
key: scope.key ?? "",
|
|
2363
2731
|
within: scope.within,
|
|
2364
|
-
resources: partitioned.length === 0 ? void 0 : partitioned
|
|
2732
|
+
resources: partitioned.length === 0 ? void 0 : partitioned,
|
|
2733
|
+
fields: Object.keys(fields).length === 0 ? void 0 : fields
|
|
2365
2734
|
});
|
|
2366
2735
|
});
|
|
2367
2736
|
}
|
|
@@ -2457,7 +2826,8 @@ function buildInstance(policy, subject, envBase, team) {
|
|
|
2457
2826
|
scope: permission.scope,
|
|
2458
2827
|
resource: resourceRef,
|
|
2459
2828
|
subject,
|
|
2460
|
-
message: deniedMessage(permission.key, subject.principal?.id, decision.denials, decision.alternatives.map((leaf) => leaf.key))
|
|
2829
|
+
message: deniedMessage(permission.key, subject.principal?.id, decision.denials, decision.alternatives.map((leaf) => leaf.key)),
|
|
2830
|
+
...data === void 0 || resource === void 0 ? {} : { disclosure: resource.disclosure }
|
|
2461
2831
|
});
|
|
2462
2832
|
};
|
|
2463
2833
|
const instance = {
|
|
@@ -2473,7 +2843,10 @@ function buildInstance(policy, subject, envBase, team) {
|
|
|
2473
2843
|
let granted = 0;
|
|
2474
2844
|
let denied = 0;
|
|
2475
2845
|
let approvalRequired = 0;
|
|
2476
|
-
const quiet =
|
|
2846
|
+
const quiet = {
|
|
2847
|
+
...envFor(false, true),
|
|
2848
|
+
outcomeOnly: true
|
|
2849
|
+
};
|
|
2477
2850
|
const trusted = options?.trusted ?? true;
|
|
2478
2851
|
const decideOptions = {
|
|
2479
2852
|
...options,
|
|
@@ -2533,6 +2906,7 @@ function buildInstance(policy, subject, envBase, team) {
|
|
|
2533
2906
|
graph: envBase.relations !== void 0,
|
|
2534
2907
|
scopes: scopeList(policy.scopes),
|
|
2535
2908
|
partitioned: (name, key) => relatesTo(getResource(policy.permissions, permission.resource), key, name, scopeList(policy.scopes)),
|
|
2909
|
+
fieldOf: (name) => scopeField(getResource(policy.permissions, permission.resource), name, scopeList(policy.scopes)),
|
|
2536
2910
|
tenant: subject.principal?.tenant,
|
|
2537
2911
|
team,
|
|
2538
2912
|
now: nowSeconds(),
|
|
@@ -2615,7 +2989,7 @@ function buildInstance(policy, subject, envBase, team) {
|
|
|
2615
2989
|
relationCache
|
|
2616
2990
|
}, team);
|
|
2617
2991
|
}),
|
|
2618
|
-
snapshot(options) {
|
|
2992
|
+
snapshot: ((options) => {
|
|
2619
2993
|
const snapshot = snapshotOf(policy, subject, compact({
|
|
2620
2994
|
customRoles: envBase.customRoles,
|
|
2621
2995
|
customGrants,
|
|
@@ -2626,7 +3000,7 @@ function buildInstance(policy, subject, envBase, team) {
|
|
|
2626
3000
|
}));
|
|
2627
3001
|
if (options?.signer !== void 0) return signSnapshot(snapshot, options.signer, options.audience);
|
|
2628
3002
|
return snapshot;
|
|
2629
|
-
},
|
|
3003
|
+
}),
|
|
2630
3004
|
on(event, handler) {
|
|
2631
3005
|
const set = listeners[event];
|
|
2632
3006
|
set.add(handler);
|
|
@@ -2687,24 +3061,111 @@ function buildInstance(policy, subject, envBase, team) {
|
|
|
2687
3061
|
const tenant = options?.tenant ?? subject.principal?.tenant;
|
|
2688
3062
|
return tenant === void 0 ? [] : assignableAt(tenant).permissions;
|
|
2689
3063
|
},
|
|
3064
|
+
assignableLevels(permission, options) {
|
|
3065
|
+
const of = (found) => (Object.hasOwn(found.levels, permission.key) ? found.levels[permission.key] : void 0) ?? [];
|
|
3066
|
+
if (options?.scope === "global") return of(assignableIn(policy, subject, envBase.customRoles, customGrants, void 0, void 0, nowSeconds(), true));
|
|
3067
|
+
const tenant = options?.tenant ?? subject.principal?.tenant;
|
|
3068
|
+
return tenant === void 0 ? [] : of(assignableAt(tenant));
|
|
3069
|
+
},
|
|
2690
3070
|
decideRoleChange(change, options) {
|
|
2691
|
-
return decideRoleChange(policy, subject.principal, scopeList(policy.scopes), change, (tenant) => {
|
|
3071
|
+
return decideRoleChange(policy, subject.principal, scopeList(policy.scopes), change, envBase.customRoles, (tenant) => {
|
|
2692
3072
|
const found = assignableAt(tenant);
|
|
2693
3073
|
return {
|
|
2694
3074
|
assignable: new Set(found.roles.map((leaf) => leaf.key)),
|
|
2695
|
-
manage: found.manage
|
|
3075
|
+
manage: found.manage,
|
|
3076
|
+
inert: new Set(found.inert)
|
|
2696
3077
|
};
|
|
2697
3078
|
}, nowSeconds(), options);
|
|
2698
3079
|
},
|
|
2699
3080
|
activate(input) {
|
|
2700
3081
|
return activate(policy, subject, input, nowSeconds());
|
|
2701
3082
|
},
|
|
3083
|
+
derive(options) {
|
|
3084
|
+
const scopes = scopeList(policy.scopes);
|
|
3085
|
+
const tenants = tenantsOf(subject.principal, scopes);
|
|
3086
|
+
const auth = [];
|
|
3087
|
+
const named = options.customRoles === void 0 ? void 0 : roleSourceFor(options.customRoles, subject, auth);
|
|
3088
|
+
const roleSource = options.customRoles === void 0 ? envBase.roleSource : named;
|
|
3089
|
+
const roles = options.customRoles === void 0 ? envBase.customRoles : customRolesFor(named, tenants, auth, subject.principal === null ? void 0 : subject.principal.roles ?? [], (tenant) => heldRoleNamesIn(subject.principal, scopes, tenant));
|
|
3090
|
+
const names = options.customRoles === void 0 ? envBase.assignable : assignableNamesFor(named, tenants, auth);
|
|
3091
|
+
const approvals = options.approvalPolicies === void 0 ? envBase.approvalPolicies : approvalPoliciesFor(policy, options.approvalPolicies, tenants, auth);
|
|
3092
|
+
const relationSource = options.relations ?? envBase.relations;
|
|
3093
|
+
const build = (loadedRoles, loadedNames, loadedApprovals) => buildInstance(policy, subject, compact({
|
|
3094
|
+
...envBase,
|
|
3095
|
+
customRoles: loadedRoles,
|
|
3096
|
+
roleSource,
|
|
3097
|
+
assignable: loadedNames,
|
|
3098
|
+
approvalPolicies: loadedApprovals,
|
|
3099
|
+
relations: relationSource,
|
|
3100
|
+
relationCache: options.relations === void 0 ? relationCache : void 0,
|
|
3101
|
+
queuedAuth: auth,
|
|
3102
|
+
queuedErrors: []
|
|
3103
|
+
}), team);
|
|
3104
|
+
if (isThenable(roles) || isThenable(names) || isThenable(approvals)) return Promise.all([
|
|
3105
|
+
roles,
|
|
3106
|
+
names,
|
|
3107
|
+
approvals
|
|
3108
|
+
]).then(([loadedRoles, loadedNames, loadedApprovals]) => build(loadedRoles, loadedNames, loadedApprovals));
|
|
3109
|
+
return build(roles, names, approvals);
|
|
3110
|
+
},
|
|
2702
3111
|
subject
|
|
2703
3112
|
};
|
|
2704
3113
|
return Object.freeze(instance);
|
|
2705
3114
|
}
|
|
2706
3115
|
//#endregion
|
|
3116
|
+
//#region src/core/oauth-scopes.ts
|
|
3117
|
+
/**
|
|
3118
|
+
* `delegation` with every coarse OAuth scope the policy declares in
|
|
3119
|
+
* `oauthScopes` followed by the scopes of the permissions it covers, so the
|
|
3120
|
+
* delegation checks downstream match them. The token's scopes stay first and
|
|
3121
|
+
* unchanged; an undeclared scope expands to nothing.
|
|
3122
|
+
*/
|
|
3123
|
+
function expandOAuthScopes(policy, delegation) {
|
|
3124
|
+
const declared = policy.oauthScopes;
|
|
3125
|
+
const scopes = delegation?.scopes;
|
|
3126
|
+
if (declared === void 0 || delegation === void 0 || scopes === void 0) return delegation;
|
|
3127
|
+
const out = new Set(scopes);
|
|
3128
|
+
for (const entry of declared) if (scopes.includes(entry.scope)) for (const scope of entry.scopes) out.add(scope);
|
|
3129
|
+
return out.size === scopes.length ? delegation : {
|
|
3130
|
+
...delegation,
|
|
3131
|
+
scopes: [...out]
|
|
3132
|
+
};
|
|
3133
|
+
}
|
|
3134
|
+
/** The coarse scopes the policy declares that cover `permission`, in declaration order. */
|
|
3135
|
+
function coarseScopesFor(policy, permission) {
|
|
3136
|
+
return (policy.oauthScopes ?? []).filter((entry) => entry.permissions.includes(permission.key)).map((entry) => entry.scope);
|
|
3137
|
+
}
|
|
3138
|
+
/** Every token scope that reaches `permission`: its own, any it was renamed from, and the coarse scopes covering it. */
|
|
3139
|
+
function scopesReaching(policy, permission) {
|
|
3140
|
+
return [
|
|
3141
|
+
permission.scope,
|
|
3142
|
+
...formerScopes(permission),
|
|
3143
|
+
...coarseScopesFor(policy, permission)
|
|
3144
|
+
];
|
|
3145
|
+
}
|
|
3146
|
+
/**
|
|
3147
|
+
* The scope an `insufficient_scope` challenge names for `permission`: the
|
|
3148
|
+
* first coarse scope that covers it, which the authorization server issues,
|
|
3149
|
+
* else the permission's own scope.
|
|
3150
|
+
*/
|
|
3151
|
+
function challengeScope(policy, permission) {
|
|
3152
|
+
return coarseScopesFor(policy, permission)[0] ?? permission.scope;
|
|
3153
|
+
}
|
|
3154
|
+
//#endregion
|
|
2707
3155
|
//#region src/core/resolve-subject.ts
|
|
3156
|
+
/**
|
|
3157
|
+
* The tenant a user API key holds its owner to (`Credential.tenant`): only
|
|
3158
|
+
* the memberships inside it count, and no global role.
|
|
3159
|
+
*/
|
|
3160
|
+
function heldTenantOf(principal) {
|
|
3161
|
+
const credential = principal["credential"];
|
|
3162
|
+
if (credential === null || typeof credential !== "object" || !Object.hasOwn(credential, "tenant") || !Object.hasOwn(credential, "kind")) return;
|
|
3163
|
+
const record = credential;
|
|
3164
|
+
return record.kind === "user" && typeof record.tenant === "string" && record.tenant !== "" ? record.tenant : void 0;
|
|
3165
|
+
}
|
|
3166
|
+
function outsideHeld(held, options) {
|
|
3167
|
+
return held !== void 0 && options.tenant !== void 0 && options.tenant !== held;
|
|
3168
|
+
}
|
|
2708
3169
|
function assemblePrincipal(policy, user, options, auth) {
|
|
2709
3170
|
let principal;
|
|
2710
3171
|
let context = {};
|
|
@@ -2712,6 +3173,7 @@ function assemblePrincipal(policy, user, options, auth) {
|
|
|
2712
3173
|
let delegation = options.delegation;
|
|
2713
3174
|
let session = options.session;
|
|
2714
3175
|
let expiresAt = options.expiresAt;
|
|
3176
|
+
let liveSession;
|
|
2715
3177
|
try {
|
|
2716
3178
|
if (user === null || user === void 0) principal = policy.subject(user);
|
|
2717
3179
|
else if (isSubject(user)) {
|
|
@@ -2721,6 +3183,7 @@ function assemblePrincipal(policy, user, options, auth) {
|
|
|
2721
3183
|
delegation = user.delegation ?? delegation;
|
|
2722
3184
|
session = user.session ?? session;
|
|
2723
3185
|
expiresAt = user.expiresAt ?? expiresAt;
|
|
3186
|
+
liveSession = user.liveSession === true ? true : void 0;
|
|
2724
3187
|
} else if (isPrincipal(user)) principal = user;
|
|
2725
3188
|
else principal = policy.subject(user);
|
|
2726
3189
|
} catch {
|
|
@@ -2731,30 +3194,73 @@ function assemblePrincipal(policy, user, options, auth) {
|
|
|
2731
3194
|
let live = false;
|
|
2732
3195
|
const source = options.memberships === void 0 ? void 0 : asMembershipSource(options.memberships);
|
|
2733
3196
|
const fromClaims = source?.claimsFirst === true && principal?.memberships !== void 0 && principal.membershipsTruncated !== true;
|
|
2734
|
-
|
|
2735
|
-
live = true;
|
|
3197
|
+
const read = (from, of) => {
|
|
2736
3198
|
try {
|
|
2737
|
-
|
|
2738
|
-
id:
|
|
2739
|
-
kind:
|
|
3199
|
+
return from.membershipsFor(compact({
|
|
3200
|
+
id: of.id,
|
|
3201
|
+
kind: of.kind
|
|
2740
3202
|
}), compact({ tenant: options.tenant }));
|
|
2741
3203
|
} catch {
|
|
2742
3204
|
auth.push({
|
|
2743
3205
|
reason: "source-threw",
|
|
2744
3206
|
source: "memberships"
|
|
2745
3207
|
});
|
|
2746
|
-
|
|
3208
|
+
return [];
|
|
2747
3209
|
}
|
|
3210
|
+
};
|
|
3211
|
+
let stale;
|
|
3212
|
+
if (principal !== null && source !== void 0 && !fromClaims) {
|
|
3213
|
+
live = true;
|
|
3214
|
+
memberships = read(source, principal);
|
|
3215
|
+
} else if (principal?.memberships !== void 0 && fromClaims && source.onStale === "reread" && source.version !== void 0) {
|
|
3216
|
+
const reread = rereadStale(source, principal, principal.memberships, () => read(source, principal), auth);
|
|
3217
|
+
memberships = reread.memberships;
|
|
3218
|
+
stale = reread.stale;
|
|
2748
3219
|
}
|
|
2749
|
-
return {
|
|
3220
|
+
return compact({
|
|
2750
3221
|
principal,
|
|
2751
3222
|
context: contextResult,
|
|
2752
3223
|
actor,
|
|
2753
|
-
delegation,
|
|
3224
|
+
delegation: expandOAuthScopes(policy, delegation),
|
|
2754
3225
|
session,
|
|
2755
3226
|
expiresAt,
|
|
2756
3227
|
memberships,
|
|
2757
|
-
live
|
|
3228
|
+
live,
|
|
3229
|
+
stale,
|
|
3230
|
+
liveSession: principal === null ? void 0 : liveSession
|
|
3231
|
+
});
|
|
3232
|
+
}
|
|
3233
|
+
function versionQuery(principal) {
|
|
3234
|
+
return compact({
|
|
3235
|
+
id: principal.id,
|
|
3236
|
+
roles: principal.roles,
|
|
3237
|
+
memberships: principal.memberships
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
function rereadStale(source, principal, claims, read, auth) {
|
|
3241
|
+
const claimed = principal.authzVersion;
|
|
3242
|
+
const judge = (current) => typeof claimed === "number" && current !== void 0 && claimed >= current ? "fresh" : "reread";
|
|
3243
|
+
const unknown = () => {
|
|
3244
|
+
auth.push({
|
|
3245
|
+
reason: "source-threw",
|
|
3246
|
+
source: "memberships"
|
|
3247
|
+
});
|
|
3248
|
+
return "unknown";
|
|
3249
|
+
};
|
|
3250
|
+
let freshness;
|
|
3251
|
+
try {
|
|
3252
|
+
const current = source.version?.(versionQuery(principal));
|
|
3253
|
+
freshness = isThenable(current) ? Promise.resolve(current).then(judge, unknown) : judge(current);
|
|
3254
|
+
} catch {
|
|
3255
|
+
freshness = unknown();
|
|
3256
|
+
}
|
|
3257
|
+
if (isThenable(freshness)) return {
|
|
3258
|
+
memberships: freshness.then((found) => found === "reread" ? read() : claims),
|
|
3259
|
+
stale: freshness.then((found) => found === "unknown")
|
|
3260
|
+
};
|
|
3261
|
+
return {
|
|
3262
|
+
memberships: freshness === "reread" ? read() : claims,
|
|
3263
|
+
stale: freshness === "unknown"
|
|
2758
3264
|
};
|
|
2759
3265
|
}
|
|
2760
3266
|
function finishSubject(policy, assembled, context, input, options, extra) {
|
|
@@ -2766,7 +3272,10 @@ function finishSubject(policy, assembled, context, input, options, extra) {
|
|
|
2766
3272
|
expiresAt: assembled.expiresAt
|
|
2767
3273
|
}));
|
|
2768
3274
|
const scopes = scopeList(policy.scopes);
|
|
2769
|
-
const
|
|
3275
|
+
const held = heldTenantOf(assembled.principal);
|
|
3276
|
+
const kinds = applyRoleKinds(policy, assembled.principal.roles, normalizeMemberships(input, scopes));
|
|
3277
|
+
const roles = held === void 0 ? kinds.roles : [];
|
|
3278
|
+
const memberships = held === void 0 ? kinds.memberships : outsideHeld(held, options) ? [] : kinds.memberships.filter((membership) => tenantOf(membership, scopes) === held);
|
|
2770
3279
|
const plans = [.../* @__PURE__ */ new Set([...assembled.principal.plans ?? [], ...extra.plans])];
|
|
2771
3280
|
const withMemberships = freezeCopy(compact({
|
|
2772
3281
|
...assembled.principal,
|
|
@@ -2782,11 +3291,14 @@ function finishSubject(policy, assembled, context, input, options, extra) {
|
|
|
2782
3291
|
context: freezeCopy(context),
|
|
2783
3292
|
session: assembled.session,
|
|
2784
3293
|
expiresAt: assembled.expiresAt,
|
|
2785
|
-
stale: extra.stale ? true : void 0
|
|
3294
|
+
stale: extra.stale ? true : void 0,
|
|
3295
|
+
liveSession: assembled.liveSession
|
|
2786
3296
|
}));
|
|
2787
3297
|
}
|
|
2788
3298
|
function activeTenantOf(policy, assembled, memberships, options) {
|
|
2789
3299
|
if (assembled.principal === null) return;
|
|
3300
|
+
const held = heldTenantOf(assembled.principal);
|
|
3301
|
+
if (held !== void 0) return outsideHeld(held, options) ? void 0 : held;
|
|
2790
3302
|
return resolveActiveTenant({
|
|
2791
3303
|
...assembled.principal,
|
|
2792
3304
|
memberships
|
|
@@ -2807,12 +3319,13 @@ function settle(value, fallback, onError) {
|
|
|
2807
3319
|
function staleness(policy, assembled, options, auth) {
|
|
2808
3320
|
const principal = assembled.principal;
|
|
2809
3321
|
if ((policy.fresh ?? []).length === 0 || principal === null || assembled.live) return false;
|
|
3322
|
+
if (assembled.stale !== void 0) return assembled.stale;
|
|
2810
3323
|
const source = options.memberships === void 0 ? void 0 : asMembershipSource(options.memberships);
|
|
2811
3324
|
const claimed = principal.authzVersion;
|
|
2812
3325
|
if (source?.version === void 0 || typeof claimed !== "number") return true;
|
|
2813
3326
|
const compare = (current) => current === void 0 || claimed < current;
|
|
2814
3327
|
try {
|
|
2815
|
-
const current = source.version(
|
|
3328
|
+
const current = source.version(versionQuery(principal));
|
|
2816
3329
|
return isThenable(current) ? settle(Promise.resolve(current).then(compare), true, () => {
|
|
2817
3330
|
auth.push({
|
|
2818
3331
|
reason: "source-threw",
|
|
@@ -2832,7 +3345,7 @@ function cleanNames(list) {
|
|
|
2832
3345
|
}
|
|
2833
3346
|
function entitlementsOf(assembled, tenant, options, auth) {
|
|
2834
3347
|
const source = options.entitlements;
|
|
2835
|
-
if (source === void 0 || assembled.principal === null) return [];
|
|
3348
|
+
if (source === void 0 || assembled.principal === null || outsideHeld(heldTenantOf(assembled.principal), options)) return [];
|
|
2836
3349
|
try {
|
|
2837
3350
|
const found = source.entitlementsFor({ id: assembled.principal.id }, compact({ tenant }));
|
|
2838
3351
|
return isThenable(found) ? settle(Promise.resolve(found).then(cleanNames), [], () => {
|
|
@@ -2921,9 +3434,11 @@ function hostedPolicy(policy, source) {
|
|
|
2921
3434
|
}
|
|
2922
3435
|
function instantiate(codePolicy, subject, options, auth) {
|
|
2923
3436
|
const { policy, errors } = hostedPolicy(codePolicy, options.policies);
|
|
2924
|
-
const
|
|
2925
|
-
const
|
|
2926
|
-
const
|
|
3437
|
+
const scopes = scopeList(policy.scopes);
|
|
3438
|
+
const tenants = tenantsOf(subject.principal, scopes);
|
|
3439
|
+
const roleSource = roleSourceFor(options.customRoles, subject, auth);
|
|
3440
|
+
const customRoles = customRolesFor(roleSource, tenants, auth, subject.principal === null ? void 0 : subject.principal.roles ?? [], (tenant) => heldRoleNamesIn(subject.principal, scopes, tenant));
|
|
3441
|
+
const assignable = assignableNamesFor(roleSource, tenants, auth);
|
|
2927
3442
|
const approvals = approvalPoliciesFor(policy, options.approvalPolicies, tenants, auth);
|
|
2928
3443
|
const build = (roles, names, approvalPolicies) => buildInstance(policy, subject, compact({
|
|
2929
3444
|
customRoles: roles,
|
|
@@ -2931,7 +3446,7 @@ function instantiate(codePolicy, subject, options, auth) {
|
|
|
2931
3446
|
limits: options.limits,
|
|
2932
3447
|
limitCache: /* @__PURE__ */ new Map(),
|
|
2933
3448
|
simulated: false,
|
|
2934
|
-
roleSource
|
|
3449
|
+
roleSource,
|
|
2935
3450
|
assignable: names,
|
|
2936
3451
|
queuedAuth: auth,
|
|
2937
3452
|
queuedErrors: errors,
|
|
@@ -2953,4 +3468,4 @@ function createPermDock(policy, user, options = {}) {
|
|
|
2953
3468
|
return instantiate(policy, subject, options, auth);
|
|
2954
3469
|
}
|
|
2955
3470
|
//#endregion
|
|
2956
|
-
export { approvalPoliciesFor as _,
|
|
3471
|
+
export { countHolders as C, memoryApprovalPolicies as D, approvalPoliciesFor as E, validateApprovalPolicy as O, composeMemberships as S, APPROVAL_POLICY_UNAVAILABLE as T, leveled as _, collectSnapshotGrants as a, validateCustomRole as b, rankRoles as c, breakGlass as d, supportAccess as f, customRoleScope as g, customRoleClaim as h, scopesReaching as i, usesAssigns as l, ceilingGrants as m, resolveSubject as n, snapshotOf as o, andWhere as p, challengeScope as r, snapshotScopes as s, createPermDock as t, declaredRoleNames as u, membershipsClaim as v, isExternallyManaged as w, claimsFirst as x, resolveCustomRole as y };
|