@nmnmcc/ability 1.0.0 → 2.0.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 +143 -87
- package/dist/Authorizer.d.ts +43 -47
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js +5 -290
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +164 -332
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +75 -890
- package/dist/Condition.js.map +1 -1
- package/dist/Decision.d.ts +13 -13
- package/dist/Decision.d.ts.map +1 -1
- package/dist/Decision.js.map +1 -1
- package/dist/Domain.d.ts +10 -21
- package/dist/Domain.d.ts.map +1 -1
- package/dist/Domain.js +4 -62
- package/dist/Domain.js.map +1 -1
- package/dist/HttpApi.d.ts +31 -7
- package/dist/HttpApi.d.ts.map +1 -1
- package/dist/HttpApi.js +28 -11
- package/dist/HttpApi.js.map +1 -1
- package/dist/Limits.d.ts +114 -0
- package/dist/Limits.d.ts.map +1 -0
- package/dist/Limits.js +132 -0
- package/dist/Limits.js.map +1 -0
- package/dist/Policy.d.ts +274 -272
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +71 -1180
- package/dist/Policy.js.map +1 -1
- package/dist/Resource.d.ts +8 -181
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +8 -807
- package/dist/Resource.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +4 -5
- package/dist/internal/algebra.js +267 -0
- package/dist/internal/algebra.js.map +1 -0
- package/dist/internal/analysis.js +245 -0
- package/dist/internal/analysis.js.map +1 -0
- package/dist/internal/authorizer.d.ts +30 -0
- package/dist/internal/authorizer.d.ts.map +1 -0
- package/dist/internal/authorizer.js +221 -0
- package/dist/internal/authorizer.js.map +1 -0
- package/dist/internal/bind.js +286 -0
- package/dist/internal/bind.js.map +1 -0
- package/dist/internal/catalog.d.ts +56 -0
- package/dist/internal/catalog.d.ts.map +1 -0
- package/dist/internal/catalog.js +292 -0
- package/dist/internal/catalog.js.map +1 -0
- package/dist/internal/decide.js +166 -0
- package/dist/internal/decide.js.map +1 -0
- package/dist/internal/domain.js +32 -0
- package/dist/internal/domain.js.map +1 -0
- package/dist/internal/json.js +134 -0
- package/dist/internal/json.js.map +1 -0
- package/dist/internal/limits.d.ts +14 -0
- package/dist/internal/limits.d.ts.map +1 -0
- package/dist/internal/limits.js +43 -0
- package/dist/internal/limits.js.map +1 -0
- package/dist/internal/nodes.d.ts +121 -0
- package/dist/internal/nodes.d.ts.map +1 -0
- package/dist/internal/nodes.js +277 -0
- package/dist/internal/nodes.js.map +1 -0
- package/dist/internal/patterns.js +110 -0
- package/dist/internal/patterns.js.map +1 -0
- package/dist/internal/policy-json.js +255 -0
- package/dist/internal/policy-json.js.map +1 -0
- package/dist/internal/policy-nodes.d.ts +61 -0
- package/dist/internal/policy-nodes.d.ts.map +1 -0
- package/dist/internal/policy-nodes.js +146 -0
- package/dist/internal/policy-nodes.js.map +1 -0
- package/dist/internal/policy.d.ts +42 -0
- package/dist/internal/policy.d.ts.map +1 -0
- package/dist/internal/policy.js +124 -0
- package/dist/internal/policy.js.map +1 -0
- package/dist/internal/read.js +39 -0
- package/dist/internal/read.js.map +1 -0
- package/dist/internal/resource.js +115 -0
- package/dist/internal/resource.js.map +1 -0
- package/dist/internal/rule.d.ts +26 -0
- package/dist/internal/rule.d.ts.map +1 -0
- package/dist/internal/rule.js +93 -0
- package/dist/internal/rule.js.map +1 -0
- package/dist/internal/search.d.ts +22 -0
- package/dist/internal/search.d.ts.map +1 -0
- package/dist/internal/search.js +274 -0
- package/dist/internal/search.js.map +1 -0
- package/dist/internal/suggest.js +46 -0
- package/dist/internal/suggest.js.map +1 -0
- package/package.json +4 -5
- package/src/Authorizer.ts +69 -585
- package/src/Condition.ts +339 -2095
- package/src/Decision.ts +6 -5
- package/src/Domain.ts +13 -70
- package/src/HttpApi.ts +75 -13
- package/src/Limits.ts +132 -0
- package/src/Policy.ts +386 -2772
- package/src/Resource.ts +14 -1555
- package/src/index.ts +3 -9
- package/src/internal/algebra.ts +482 -0
- package/src/internal/analysis.ts +478 -0
- package/src/internal/authorizer.ts +418 -0
- package/src/internal/bind.ts +596 -0
- package/src/internal/catalog.ts +534 -0
- package/src/internal/decide.ts +402 -0
- package/src/internal/domain.ts +66 -0
- package/src/internal/json.ts +260 -0
- package/src/internal/limits.ts +78 -0
- package/src/internal/nodes.ts +404 -0
- package/src/internal/patterns.ts +211 -0
- package/src/internal/policy-json.ts +484 -0
- package/src/internal/policy-nodes.ts +207 -0
- package/src/internal/policy.ts +264 -0
- package/src/internal/read.ts +55 -0
- package/src/internal/resource.ts +180 -0
- package/src/internal/rule.ts +181 -0
- package/src/internal/search.ts +541 -0
- package/src/internal/suggest.ts +104 -0
- package/dist/Principal.d.ts +0 -35
- package/dist/Principal.d.ts.map +0 -1
- package/dist/Principal.js +0 -37
- package/dist/Principal.js.map +0 -1
- package/dist/Rule.d.ts +0 -275
- package/dist/Rule.d.ts.map +0 -1
- package/dist/Rule.js +0 -257
- package/dist/Rule.js.map +0 -1
- package/src/Principal.ts +0 -41
- package/src/Rule.ts +0 -683
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodes.js","names":["Limits.exceeded","Limits.conditionNodes"],"sources":["../../src/internal/nodes.ts"],"sourcesContent":["/**\n * The nodes of a condition. Each node is a class, and only its constructor\n * makes one, also when a condition comes from JSON. The constructor checks the\n * data of the node and works out three facts once:\n * - `size`: the number of nodes, with a shared child counted for each place;\n * - `depth`: the number of levels;\n * - `opaque`: the node has a predicate.\n *\n * The constructor stops a node that goes past `conditionNodes` or\n * `conditionDepth`. So a walk over any condition visits at most\n * `conditionNodes` nodes and goes at most `conditionDepth` levels down, and it\n * needs no memo.\n *\n * The facts are private fields, so `Equal` and `Hash` do not read them: two\n * nodes with the same data are equal.\n */\n// A predicate gives an Effect with an error type and a service type that the\n// nodes do not know, so they are `unknown` here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport type * as Effect from \"effect/Effect\"\nimport { hasProperty } from \"effect/Predicate\"\nimport * as Limits from \"./limits.ts\"\n\n/** A value that a condition can compare. */\nexport type Scalar = string | number | boolean | null\n\nexport type CompareOperator = \"eq\" | \"ne\" | \"lt\" | \"lte\" | \"gt\" | \"gte\"\n\n/** A fixed value. */\nexport interface Literal {\n\treadonly _tag: \"Literal\"\n\treadonly value: Scalar\n}\n\n/** A field of the principal. */\nexport interface PrincipalRef {\n\treadonly _tag: \"Principal\"\n\treadonly path: string\n}\n\nexport type Operand = Literal | PrincipalRef\n\nconst operands = Data.taggedEnum<Operand>()\n\nexport const literal = (value: Scalar): Literal => operands.Literal({ value })\n\nexport const principalRef = (path: string): PrincipalRef =>\n\toperands.Principal({ path })\n\nconst compareOperators: ReadonlyArray<string> = [\n\t\"eq\",\n\t\"ne\",\n\t\"lt\",\n\t\"lte\",\n\t\"gt\",\n\t\"gte\",\n]\n\n/** Tests if a value is a scalar. Any number is one, also `NaN`. */\nexport const isScalar = (value: unknown): value is Scalar =>\n\tvalue === null ||\n\ttypeof value === \"string\" ||\n\ttypeof value === \"number\" ||\n\ttypeof value === \"boolean\"\n\n/**\n * Checks a field path: a string with at most `fieldPathParts` parts. A longer\n * path is never a field of a resource or of the principal.\n */\nconst checkPath = (path: unknown): string => {\n\tif (typeof path !== \"string\") {\n\t\tthrow new Error(\"A field path must be a string\")\n\t}\n\tconst parts = path.split(\".\", Limits.fieldPathParts + 1).length\n\tif (parts > Limits.fieldPathParts) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A field path\",\n\t\t\t\"fieldPathParts\",\n\t\t\t\"parts\",\n\t\t\tparts,\n\t\t\tLimits.fieldPathParts,\n\t\t)\n\t}\n\treturn path\n}\n\n/** Checks an operand, and gives a new one with only its data. */\nconst checkOperand = (operand: unknown): Operand => {\n\tif (hasProperty(operand, \"_tag\")) {\n\t\tif (\n\t\t\toperand._tag === \"Literal\" &&\n\t\t\thasProperty(operand, \"value\") &&\n\t\t\tisScalar(operand.value)\n\t\t) {\n\t\t\treturn literal(operand.value)\n\t\t}\n\t\tif (operand._tag === \"Principal\" && hasProperty(operand, \"path\")) {\n\t\t\treturn principalRef(checkPath(operand.path))\n\t\t}\n\t}\n\tthrow new Error(\n\t\t\"An operand must be a fixed scalar value or a field of the principal\",\n\t)\n}\n\nconst checkOperands = (values: unknown): ReadonlyArray<Operand> => {\n\tif (!Array.isArray(values)) {\n\t\tthrow new Error(\"A list of operands must be an array\")\n\t}\n\treturn Object.freeze(Arr.map(Array.from(values), checkOperand))\n}\n\ninterface Facts {\n\treadonly size: number\n\treadonly depth: number\n\treadonly opaque: boolean\n}\n\nconst leaf: Facts = { size: 1, depth: 1, opaque: false }\n\n/** The facts of a node with children. It stops a node past the limits. */\nconst factsOf = (children: ReadonlyArray<Node>): Facts => {\n\tconst size = Arr.reduce(children, 1, (total, child) => total + child.size)\n\tif (size > Limits.conditionNodes) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A condition\",\n\t\t\t\"conditionNodes\",\n\t\t\t\"nodes\",\n\t\t\tsize,\n\t\t\tLimits.conditionNodes,\n\t\t)\n\t}\n\tconst depth =\n\t\t1 + Arr.reduce(children, 0, (top, child) => Math.max(top, child.depth))\n\tif (depth > Limits.conditionDepth) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A condition\",\n\t\t\t\"conditionDepth\",\n\t\t\t\"levels\",\n\t\t\tdepth,\n\t\t\tLimits.conditionDepth,\n\t\t)\n\t}\n\treturn {\n\t\tsize,\n\t\tdepth,\n\t\topaque: Arr.some(children, (child) => child.opaque),\n\t}\n}\n\n/** The base of every node. It keeps the facts. */\nabstract class Base extends Data.Class {\n\t/**\n\t * The JSON form of the node, for `JSON.stringify`. A number that JSON can\n\t * not hold, as `Infinity`, becomes an object that no decoder takes, in\n\t * place of `null`, which would change the test.\n\t */\n\ttoJSON(): unknown {\n\t\treturn toJson(this as unknown as Node)\n\t}\n\n\treadonly #facts: Facts\n\n\tconstructor(props: object, facts: Facts) {\n\t\tsuper(props as never)\n\t\tthis.#facts = facts\n\t}\n\n\t/** The number of nodes, with a shared child counted for each place. */\n\tget size(): number {\n\t\treturn this.#facts.size\n\t}\n\n\t/** The number of levels. */\n\tget depth(): number {\n\t\treturn this.#facts.depth\n\t}\n\n\t/** The node has a predicate. */\n\tget opaque(): boolean {\n\t\treturn this.#facts.opaque\n\t}\n}\n\n/** Always true. */\nexport class True extends Base {\n\treadonly _tag = \"True\"\n\tconstructor() {\n\t\tsuper({}, leaf)\n\t}\n}\n\n/** Always false. */\nexport class False extends Base {\n\treadonly _tag = \"False\"\n\tconstructor() {\n\t\tsuper({}, leaf)\n\t}\n}\n\n/** Compares a field with a value. */\nexport class Compare extends Base {\n\treadonly _tag = \"Compare\"\n\tdeclare readonly op: CompareOperator\n\tdeclare readonly field: string\n\tdeclare readonly value: Operand\n\tconstructor(props: {\n\t\treadonly op: CompareOperator\n\t\treadonly field: string\n\t\treadonly value: Operand\n\t}) {\n\t\tif (!compareOperators.includes(props.op)) {\n\t\t\tthrow new Error(`\"${String(props.op)}\" is not a compare operator`)\n\t\t}\n\t\tsuper(\n\t\t\t{\n\t\t\t\top: props.op,\n\t\t\t\tfield: checkPath(props.field),\n\t\t\t\tvalue: checkOperand(props.value),\n\t\t\t},\n\t\t\tleaf,\n\t\t)\n\t}\n}\n\n/** True when a field is equal to one of the values. */\nexport class In extends Base {\n\treadonly _tag = \"In\"\n\tdeclare readonly field: string\n\tdeclare readonly values: ReadonlyArray<Operand>\n\tconstructor(props: {\n\t\treadonly field: string\n\t\treadonly values: ReadonlyArray<Operand>\n\t}) {\n\t\tsuper(\n\t\t\t{ field: checkPath(props.field), values: checkOperands(props.values) },\n\t\t\tleaf,\n\t\t)\n\t}\n}\n\n/** True when a field is not `null` and not missing. */\nexport class Exists extends Base {\n\treadonly _tag = \"Exists\"\n\tdeclare readonly field: string\n\tconstructor(props: { readonly field: string }) {\n\t\tsuper({ field: checkPath(props.field) }, leaf)\n\t}\n}\n\n/** True when the path of the resource starts with the prefix. */\nexport class Under extends Base {\n\treadonly _tag = \"Under\"\n\tdeclare readonly prefix: ReadonlyArray<Operand>\n\tconstructor(props: { readonly prefix: ReadonlyArray<Operand> }) {\n\t\tsuper({ prefix: checkOperands(props.prefix) }, leaf)\n\t}\n}\n\nconst checkChildren = (conditions: unknown): ReadonlyArray<Node> => {\n\tif (!Array.isArray(conditions)) {\n\t\tthrow new Error(\"The children of a node must be an array\")\n\t}\n\tconst children = Array.from(conditions as ReadonlyArray<unknown>)\n\tif (!Arr.every(children, isNode)) {\n\t\tthrow notNode()\n\t}\n\treturn Object.freeze(children)\n}\n\nexport class And extends Base {\n\treadonly _tag = \"And\"\n\tdeclare readonly conditions: ReadonlyArray<Node>\n\tconstructor(conditions: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(conditions)\n\t\tsuper({ conditions: children }, factsOf(children))\n\t}\n}\n\nexport class Or extends Base {\n\treadonly _tag = \"Or\"\n\tdeclare readonly conditions: ReadonlyArray<Node>\n\tconstructor(conditions: ReadonlyArray<Node>) {\n\t\tconst children = checkChildren(conditions)\n\t\tsuper({ conditions: children }, factsOf(children))\n\t}\n}\n\nexport class Not extends Base {\n\treadonly _tag = \"Not\"\n\tdeclare readonly condition: Node\n\tconstructor(condition: Node) {\n\t\tif (!isNode(condition)) {\n\t\t\tthrow notNode()\n\t\t}\n\t\tsuper({ condition }, factsOf([condition]))\n\t}\n}\n\n/** The test of a predicate, with no types. */\nexport type Run = (\n\tvalue: unknown,\n\tprincipal: unknown,\n) => Effect.Effect<boolean, unknown, unknown>\n\n/**\n * A test that runs an `Effect`. Its test takes inputs of the type `never`, so\n * no code can call it with a value of the wrong type: `Condition.evaluate`\n * checks the input. Use {@link runOf} inside the library.\n */\nexport class Predicate extends Base {\n\treadonly _tag = \"Predicate\"\n\tdeclare readonly name: string\n\tdeclare readonly run: (\n\t\tvalue: never,\n\t\tprincipal: never,\n\t) => Effect.Effect<boolean, unknown, unknown>\n\tconstructor(props: { readonly name: string; readonly run: Run }) {\n\t\tif (typeof props.name !== \"string\" || typeof props.run !== \"function\") {\n\t\t\tthrow new Error(\"A predicate needs a name and a test\")\n\t\t}\n\t\tsuper(\n\t\t\t{ name: props.name, run: props.run },\n\t\t\t{ size: 1, depth: 1, opaque: true },\n\t\t)\n\t}\n}\n\n/** Gives the test of a predicate, with no types. */\nexport const runOf = (node: Predicate): Run => node.run as Run\n\n/** A node of a condition. */\nexport type Node =\n\tTrue | False | Compare | In | Exists | Under | And | Or | Not | Predicate\n\nconst operandJson = (operand: Operand): unknown =>\n\toperand._tag === \"Literal\"\n\t\t? {\n\t\t\t\t_tag: \"Literal\",\n\t\t\t\t// JSON has no `Infinity`, `-Infinity` or `NaN`. Such a number\n\t\t\t\t// becomes an object that the decoder does not take.\n\t\t\t\tvalue:\n\t\t\t\t\ttypeof operand.value === \"number\" && !Number.isFinite(operand.value)\n\t\t\t\t\t\t? { nonFinite: String(operand.value) }\n\t\t\t\t\t\t: operand.value,\n\t\t\t}\n\t\t: { _tag: \"Principal\", path: operand.path }\n\n/** Gives the JSON form of a condition with no predicate. */\nexport const toJson = (node: Node): unknown => {\n\tswitch (node._tag) {\n\t\tcase \"True\":\n\t\tcase \"False\":\n\t\t\treturn { _tag: node._tag }\n\t\tcase \"Compare\":\n\t\t\treturn {\n\t\t\t\t_tag: \"Compare\",\n\t\t\t\top: node.op,\n\t\t\t\tfield: node.field,\n\t\t\t\tvalue: operandJson(node.value),\n\t\t\t}\n\t\tcase \"In\":\n\t\t\treturn {\n\t\t\t\t_tag: \"In\",\n\t\t\t\tfield: node.field,\n\t\t\t\tvalues: Arr.map(node.values, operandJson),\n\t\t\t}\n\t\tcase \"Exists\":\n\t\t\treturn { _tag: \"Exists\", field: node.field }\n\t\tcase \"Under\":\n\t\t\treturn { _tag: \"Under\", prefix: Arr.map(node.prefix, operandJson) }\n\t\tcase \"And\":\n\t\tcase \"Or\":\n\t\t\treturn { _tag: node._tag, conditions: Arr.map(node.conditions, toJson) }\n\t\tcase \"Not\":\n\t\t\treturn { _tag: \"Not\", condition: toJson(node.condition) }\n\t\tcase \"Predicate\":\n\t\t\tthrow new Error(\n\t\t\t\t`The predicate \"${node.name}\" can not become JSON. A condition with a predicate has no JSON form.`,\n\t\t\t)\n\t}\n}\n\n/** Tests if a value is a node. Only a constructor of a node makes one. */\nexport const isNode = (u: unknown): u is Node => u instanceof Base\n\nconst notNode = (): Error =>\n\tnew Error(\n\t\t\"A condition must be made with a builder or a function of Condition. A plain object is not a condition\",\n\t)\n\n/** Gives a value as a node, or throws when it is not a node. */\nexport const toNode = (u: unknown): Node => {\n\tif (!isNode(u)) {\n\t\tthrow notNode()\n\t}\n\treturn u\n}\n\nexport const always: Node = new True()\n\nexport const never: Node = new False()\n"],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { keyFits } from "./catalog.js";
|
|
2
|
+
import * as Result from "effect/Result";
|
|
3
|
+
import * as Arr from "effect/Array";
|
|
4
|
+
// src/internal/patterns.ts
|
|
5
|
+
/**
|
|
6
|
+
* Field patterns, as in `settings.*`, `secret*` or `**.secret`. A pattern is
|
|
7
|
+
* a list of parts with dots between them:
|
|
8
|
+
* - A name with no star matches that name.
|
|
9
|
+
* - A part with stars matches a name, and each star matches any text with no
|
|
10
|
+
* dot, as `secret*` matches `secretKey`.
|
|
11
|
+
* - `**` matches any number of names, also none. It must be a whole part.
|
|
12
|
+
*
|
|
13
|
+
* A pattern that matches a field matches each field below it too. So `pay`
|
|
14
|
+
* matches `pay.amount`, as `pay.**` does.
|
|
15
|
+
*
|
|
16
|
+
* The match goes one name at a time, and it keeps the set of places in the
|
|
17
|
+
* pattern that it may be at. It does not go back, so the time is at most the
|
|
18
|
+
* number of names times the number of parts.
|
|
19
|
+
*/
|
|
20
|
+
const globstar = { _tag: "Globstar" };
|
|
21
|
+
const partOf = (text) => text === "**" ? globstar : text.includes("*") ? {
|
|
22
|
+
_tag: "Glob",
|
|
23
|
+
pieces: text.split("*")
|
|
24
|
+
} : {
|
|
25
|
+
_tag: "Name",
|
|
26
|
+
name: text
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Reads a pattern. It fails with the problem when the pattern has `**`
|
|
30
|
+
* inside a part, or has more parts than `patternParts`.
|
|
31
|
+
*/
|
|
32
|
+
const parse = (text) => {
|
|
33
|
+
const texts = text.split(".", 17);
|
|
34
|
+
if (texts.length > 16) return Result.fail(`The field pattern "${text}" has more than ${String(16)} parts. The most is ${String(16)}.`);
|
|
35
|
+
if (Arr.some(texts, (part) => part !== "**" && part.includes("**"))) return Result.fail(`The field pattern "${text}" has "**" inside a part. "**" must be a whole part`);
|
|
36
|
+
return Result.succeed(Arr.dedupeAdjacentWith(Arr.map(texts, partOf), (left, right) => left._tag === "Globstar" && right._tag === "Globstar"));
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Reads a pattern of a bound rule. The binding of the rule took the pattern,
|
|
40
|
+
* so a problem here is a defect.
|
|
41
|
+
*/
|
|
42
|
+
const parseBound = (text) => Result.getOrThrowWith(parse(text), (problem) => new Error(problem));
|
|
43
|
+
/** Tests if a name matches the pieces of a part with stars. */
|
|
44
|
+
const globMatches = (pieces, name) => {
|
|
45
|
+
const first = pieces[0];
|
|
46
|
+
const last = pieces[pieces.length - 1];
|
|
47
|
+
const middle = pieces.slice(1, -1);
|
|
48
|
+
if (name.length < first.length + last.length || !name.startsWith(first) || !name.endsWith(last)) return false;
|
|
49
|
+
const end = name.length - last.length;
|
|
50
|
+
return Arr.reduce(middle, first.length, (at, piece) => {
|
|
51
|
+
if (at < 0) return at;
|
|
52
|
+
const found = name.indexOf(piece, at);
|
|
53
|
+
return found < 0 || found + piece.length > end ? -1 : found + piece.length;
|
|
54
|
+
}) >= 0;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Tests if a part matches one segment of a path. A part with stars matches
|
|
58
|
+
* any key of a record. A name matches a key of a record when the Schema of
|
|
59
|
+
* the keys takes it.
|
|
60
|
+
*/
|
|
61
|
+
const partFits = (part, segment) => {
|
|
62
|
+
switch (part._tag) {
|
|
63
|
+
case "Globstar": return true;
|
|
64
|
+
case "Glob": return typeof segment !== "string" || globMatches(part.pieces, segment);
|
|
65
|
+
case "Name": return typeof segment === "string" ? part.name === segment : keyFits(segment, part.name);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
/** Adds the place after each `**`, since a `**` may match no names. */
|
|
69
|
+
const close = (pattern, states) => Arr.dedupe(Arr.flatMap(states, (state) => pattern[state]?._tag === "Globstar" ? [state, state + 1] : [state]));
|
|
70
|
+
const start = (pattern) => close(pattern, [0]);
|
|
71
|
+
/** Gives the places after one more segment. A `**` that takes it stays. */
|
|
72
|
+
const step = (pattern, states, segment) => close(pattern, Arr.flatMap(states, (state) => {
|
|
73
|
+
const part = pattern[state];
|
|
74
|
+
return part === void 0 ? [] : part._tag === "Globstar" ? [state] : partFits(part, segment) ? [state + 1] : [];
|
|
75
|
+
}));
|
|
76
|
+
const isDone = (pattern, states) => states.includes(pattern.length);
|
|
77
|
+
/**
|
|
78
|
+
* Tests if a pattern matches a field, or a field above it. The field is a
|
|
79
|
+
* list of names.
|
|
80
|
+
*/
|
|
81
|
+
const matches = (pattern, names) => {
|
|
82
|
+
return Arr.reduce(names, {
|
|
83
|
+
states: start(pattern),
|
|
84
|
+
done: isDone(pattern, start(pattern))
|
|
85
|
+
}, (before, name) => {
|
|
86
|
+
if (before.done || before.states.length === 0) return before;
|
|
87
|
+
const states = step(pattern, before.states, name);
|
|
88
|
+
return {
|
|
89
|
+
states,
|
|
90
|
+
done: isDone(pattern, states)
|
|
91
|
+
};
|
|
92
|
+
}).done;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Tests if a pattern may match a field below a field, with more names after
|
|
96
|
+
* the names of the field.
|
|
97
|
+
*/
|
|
98
|
+
const matchesBelow = (pattern, names) => Arr.some(Arr.reduce(names, start(pattern), (states, name) => step(pattern, states, name)), (state) => state < pattern.length);
|
|
99
|
+
/** Tests if a pattern matches some field of a catalog. */
|
|
100
|
+
const fitsCatalog = (pattern, catalog) => {
|
|
101
|
+
const below = (branch, states) => states.length > 0 && (Arr.some(branch.children, ([name, child]) => visit(child, name, states)) || branch.key !== void 0 && branch.keyBranch !== void 0 && visit(branch.keyBranch, branch.key, states));
|
|
102
|
+
const visit = (branch, segment, before) => {
|
|
103
|
+
const states = step(pattern, before, segment);
|
|
104
|
+
return branch.field !== void 0 && isDone(pattern, states) || below(branch, states);
|
|
105
|
+
};
|
|
106
|
+
return below(catalog.root, start(pattern));
|
|
107
|
+
};
|
|
108
|
+
export { fitsCatalog, matches, matchesBelow, parse, parseBound };
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=patterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns.js","names":["Catalog.keyFits"],"sources":["../../src/internal/patterns.ts"],"sourcesContent":["/**\n * Field patterns, as in `settings.*`, `secret*` or `**.secret`. A pattern is\n * a list of parts with dots between them:\n * - A name with no star matches that name.\n * - A part with stars matches a name, and each star matches any text with no\n * dot, as `secret*` matches `secretKey`.\n * - `**` matches any number of names, also none. It must be a whole part.\n *\n * A pattern that matches a field matches each field below it too. So `pay`\n * matches `pay.amount`, as `pay.**` does.\n *\n * The match goes one name at a time, and it keeps the set of places in the\n * pattern that it may be at. It does not go back, so the time is at most the\n * number of names times the number of parts.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Result from \"effect/Result\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\n\n/** One part of a pattern. */\nexport type Part =\n\t| { readonly _tag: \"Name\"; readonly name: string }\n\t/** The texts between the stars of the part. */\n\t| { readonly _tag: \"Glob\"; readonly pieces: ReadonlyArray<string> }\n\t| { readonly _tag: \"Globstar\" }\n\n/** A pattern, as a list of parts. Two `**` parts are never next to each other. */\nexport type Pattern = ReadonlyArray<Part>\n\nconst globstar: Part = { _tag: \"Globstar\" }\n\nconst partOf = (text: string): Part =>\n\ttext === \"**\"\n\t\t? globstar\n\t\t: text.includes(\"*\")\n\t\t\t? { _tag: \"Glob\", pieces: text.split(\"*\") }\n\t\t\t: { _tag: \"Name\", name: text }\n\n/**\n * Reads a pattern. It fails with the problem when the pattern has `**`\n * inside a part, or has more parts than `patternParts`.\n */\nexport const parse = (text: string): Result.Result<Pattern, string> => {\n\tconst texts = text.split(\".\", Limits.patternParts + 1)\n\tif (texts.length > Limits.patternParts) {\n\t\treturn Result.fail(\n\t\t\t`The field pattern \"${text}\" has more than ${String(Limits.patternParts)} parts. The most is ${String(Limits.patternParts)}.`,\n\t\t)\n\t}\n\tif (Arr.some(texts, (part) => part !== \"**\" && part.includes(\"**\"))) {\n\t\treturn Result.fail(\n\t\t\t`The field pattern \"${text}\" has \"**\" inside a part. \"**\" must be a whole part`,\n\t\t)\n\t}\n\t// `a.**.**` matches what `a.**` matches.\n\treturn Result.succeed(\n\t\tArr.dedupeAdjacentWith(\n\t\t\tArr.map(texts, partOf),\n\t\t\t(left, right) => left._tag === \"Globstar\" && right._tag === \"Globstar\",\n\t\t),\n\t)\n}\n\n/**\n * Reads a pattern of a bound rule. The binding of the rule took the pattern,\n * so a problem here is a defect.\n */\nexport const parseBound = (text: string): Pattern =>\n\tResult.getOrThrowWith(parse(text), (problem) => new Error(problem))\n\n/** Tests if a name matches the pieces of a part with stars. */\nconst globMatches = (pieces: ReadonlyArray<string>, name: string): boolean => {\n\tconst first = pieces[0]!\n\tconst last = pieces[pieces.length - 1]!\n\tconst middle = pieces.slice(1, -1)\n\tif (\n\t\tname.length < first.length + last.length ||\n\t\t!name.startsWith(first) ||\n\t\t!name.endsWith(last)\n\t) {\n\t\treturn false\n\t}\n\t// Each piece in the middle is found as early as it can be. An earlier\n\t// place never leaves less room for the pieces after it.\n\tconst end = name.length - last.length\n\tconst place = Arr.reduce(middle, first.length as number, (at, piece) => {\n\t\tif (at < 0) {\n\t\t\treturn at\n\t\t}\n\t\tconst found = name.indexOf(piece, at)\n\t\treturn found < 0 || found + piece.length > end ? -1 : found + piece.length\n\t})\n\treturn place >= 0\n}\n\n/**\n * Tests if a part matches one segment of a path. A part with stars matches\n * any key of a record. A name matches a key of a record when the Schema of\n * the keys takes it.\n */\nconst partFits = (part: Part, segment: Catalog.Segment): boolean => {\n\tswitch (part._tag) {\n\t\tcase \"Globstar\":\n\t\t\treturn true\n\t\tcase \"Glob\":\n\t\t\treturn typeof segment !== \"string\" || globMatches(part.pieces, segment)\n\t\tcase \"Name\":\n\t\t\treturn typeof segment === \"string\"\n\t\t\t\t? part.name === segment\n\t\t\t\t: Catalog.keyFits(segment, part.name)\n\t}\n}\n\n/** The places of a match: the parts that it may take next. */\ntype States = ReadonlyArray<number>\n\n/** Adds the place after each `**`, since a `**` may match no names. */\nconst close = (pattern: Pattern, states: States): States =>\n\tArr.dedupe(\n\t\tArr.flatMap(states, (state) =>\n\t\t\tpattern[state]?._tag === \"Globstar\" ? [state, state + 1] : [state],\n\t\t),\n\t)\n\nconst start = (pattern: Pattern): States => close(pattern, [0])\n\n/** Gives the places after one more segment. A `**` that takes it stays. */\nconst step = (\n\tpattern: Pattern,\n\tstates: States,\n\tsegment: Catalog.Segment,\n): States =>\n\tclose(\n\t\tpattern,\n\t\tArr.flatMap(states, (state) => {\n\t\t\tconst part = pattern[state]\n\t\t\treturn part === undefined\n\t\t\t\t? []\n\t\t\t\t: part._tag === \"Globstar\"\n\t\t\t\t\t? [state]\n\t\t\t\t\t: partFits(part, segment)\n\t\t\t\t\t\t? [state + 1]\n\t\t\t\t\t\t: []\n\t\t}),\n\t)\n\nconst isDone = (pattern: Pattern, states: States): boolean =>\n\tstates.includes(pattern.length)\n\n/**\n * Tests if a pattern matches a field, or a field above it. The field is a\n * list of names.\n */\nexport const matches = (\n\tpattern: Pattern,\n\tnames: ReadonlyArray<string>,\n): boolean => {\n\tconst found = Arr.reduce(\n\t\tnames,\n\t\t{ states: start(pattern), done: isDone(pattern, start(pattern)) },\n\t\t(before, name) => {\n\t\t\tif (before.done || before.states.length === 0) {\n\t\t\t\treturn before\n\t\t\t}\n\t\t\tconst states = step(pattern, before.states, name)\n\t\t\treturn { states, done: isDone(pattern, states) }\n\t\t},\n\t)\n\treturn found.done\n}\n\n/**\n * Tests if a pattern may match a field below a field, with more names after\n * the names of the field.\n */\nexport const matchesBelow = (\n\tpattern: Pattern,\n\tnames: ReadonlyArray<string>,\n): boolean =>\n\tArr.some(\n\t\tArr.reduce(names, start(pattern), (states, name) =>\n\t\t\tstep(pattern, states, name),\n\t\t),\n\t\t(state) => state < pattern.length,\n\t)\n\n/** Tests if a pattern matches some field of a catalog. */\nexport const fitsCatalog = (\n\tpattern: Pattern,\n\tcatalog: Catalog.Catalog,\n): boolean => {\n\tconst below = (branch: Catalog.Branch, states: States): boolean =>\n\t\tstates.length > 0 &&\n\t\t(Arr.some(branch.children, ([name, child]) => visit(child, name, states)) ||\n\t\t\t(branch.key !== undefined &&\n\t\t\t\tbranch.keyBranch !== undefined &&\n\t\t\t\tvisit(branch.keyBranch, branch.key, states)))\n\tconst visit = (\n\t\tbranch: Catalog.Branch,\n\t\tsegment: Catalog.Segment,\n\t\tbefore: States,\n\t): boolean => {\n\t\tconst states = step(pattern, before, segment)\n\t\treturn (\n\t\t\t(branch.field !== undefined && isDone(pattern, states)) ||\n\t\t\tbelow(branch, states)\n\t\t)\n\t}\n\treturn below(catalog.root, start(pattern))\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { LimitExceeded, policyNodes } from "./limits.js";
|
|
2
|
+
import { Problem, decodeAt, problem, read, readList, readString, spend, toJson as toJson$1 } from "./json.js";
|
|
3
|
+
import { make } from "./rule.js";
|
|
4
|
+
import { Ceiling, Rules, policy_nodes_exports, rulesOf } from "./policy-nodes.js";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
import * as Arr from "effect/Array";
|
|
8
|
+
// src/internal/policy-json.ts
|
|
9
|
+
/**
|
|
10
|
+
* The JSON form of a policy tree. The decoder is a plain walk, as the decoder
|
|
11
|
+
* of a condition is, and the two share one budget of `jsonNodes` objects and
|
|
12
|
+
* items. It makes each node with its constructor, so the limits of the nodes
|
|
13
|
+
* hold for a decoded tree too. The binding of the rules to the domain comes
|
|
14
|
+
* after, in the Schema of the policy.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Gives the JSON form of the `meta` of each rule at once, with the `meta`
|
|
18
|
+
* Schema of a domain. `JSON.stringify` needs it at once, so the Schema must
|
|
19
|
+
* not need an async step there.
|
|
20
|
+
*/
|
|
21
|
+
const syncMeta = (meta) => (rule) => rule.meta === void 0 || meta === void 0 ? void 0 : Schema.encodeUnknownSync(meta)(rule.meta);
|
|
22
|
+
/** Gives the JSON form of a rule. The domain of a rule is not in it. */
|
|
23
|
+
const ruleJson = (rule, metaJson, whenJson) => {
|
|
24
|
+
const meta = metaJson(rule);
|
|
25
|
+
return {
|
|
26
|
+
_tag: rule._tag,
|
|
27
|
+
actions: rule.actions,
|
|
28
|
+
resources: rule.resources,
|
|
29
|
+
...rule.when === void 0 ? {} : { when: whenJson(rule) },
|
|
30
|
+
...rule.fields === void 0 ? {} : { fields: rule.fields },
|
|
31
|
+
...rule.reason === void 0 ? {} : { reason: rule.reason },
|
|
32
|
+
...rule.id === void 0 ? {} : { id: rule.id },
|
|
33
|
+
...meta === void 0 ? {} : { meta }
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Gives the JSON form of a policy tree with no predicate. A condition with a
|
|
38
|
+
* predicate has no JSON form, so it throws an error. The conditions are
|
|
39
|
+
* written first, from a flat list of the rules, so the walk does not go down
|
|
40
|
+
* the levels of the tree and of a condition at once.
|
|
41
|
+
*/
|
|
42
|
+
const toJson = (node, meta) => {
|
|
43
|
+
const whens = new Map(Arr.map(rulesOf(node), (rule) => [rule, rule.when === void 0 ? void 0 : toJson$1(rule.when)]));
|
|
44
|
+
const tree = (current) => {
|
|
45
|
+
switch (current._tag) {
|
|
46
|
+
case "Rules": return {
|
|
47
|
+
_tag: "Rules",
|
|
48
|
+
algorithm: current.algorithm,
|
|
49
|
+
rules: Arr.map(current.rules, (rule) => ruleJson(rule, meta, (found) => whens.get(found)))
|
|
50
|
+
};
|
|
51
|
+
case "All":
|
|
52
|
+
case "Any":
|
|
53
|
+
case "FirstOf": return {
|
|
54
|
+
_tag: current._tag,
|
|
55
|
+
policies: Arr.map(current.policies, tree)
|
|
56
|
+
};
|
|
57
|
+
case "Ceiling": return {
|
|
58
|
+
_tag: "Ceiling",
|
|
59
|
+
cap: tree(current.cap),
|
|
60
|
+
policy: tree(current.policy)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
return tree(node);
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the
|
|
68
|
+
* domain may need an async step, so each `meta` is encoded as an Effect.
|
|
69
|
+
*/
|
|
70
|
+
const encode = (node, meta) => Effect.gen(function* () {
|
|
71
|
+
const rules = Arr.filter(rulesOf(node), (rule) => rule.meta !== void 0);
|
|
72
|
+
const encoded = meta === void 0 ? [] : yield* Effect.forEach(rules, (rule) => Effect.map(Schema.encodeUnknownEffect(meta)(rule.meta), (json) => [rule, json]));
|
|
73
|
+
const forms = new Map(encoded);
|
|
74
|
+
return toJson(node, (rule) => forms.get(rule));
|
|
75
|
+
});
|
|
76
|
+
const algorithms = [
|
|
77
|
+
"denyOverrides",
|
|
78
|
+
"permitOverrides",
|
|
79
|
+
"firstApplicable",
|
|
80
|
+
"lastApplicable"
|
|
81
|
+
];
|
|
82
|
+
/** Reads a list of strings. */
|
|
83
|
+
const readStrings = (input, name, path, budget) => Arr.map(readList(input, name, path, budget), (value, index) => {
|
|
84
|
+
if (typeof value !== "string") throw problem([
|
|
85
|
+
...path,
|
|
86
|
+
name,
|
|
87
|
+
index
|
|
88
|
+
], "The item must be a string");
|
|
89
|
+
return value;
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
92
|
+
* Reads a field that a rule may leave out. A field that the input has only
|
|
93
|
+
* from the prototype of a class is not plain data, so it is a problem: a rule
|
|
94
|
+
* that loses its condition would allow more.
|
|
95
|
+
*/
|
|
96
|
+
const readOptional = (input, name, path, read$1) => {
|
|
97
|
+
if (Object.hasOwn(input, name)) return read$1();
|
|
98
|
+
read(input, name, path);
|
|
99
|
+
};
|
|
100
|
+
/** Counts the objects and items of a value, as the other parts count. */
|
|
101
|
+
const spendValue = (value, path, depth, budget) => {
|
|
102
|
+
if (typeof value !== "object" || value === null) return;
|
|
103
|
+
if (depth > 500) throw problem(path, `The value has more than ${String(500)} levels. The most is ${String(500)}.`);
|
|
104
|
+
const list = Array.isArray(value);
|
|
105
|
+
if (list) spend(budget, 1 + value.length, path);
|
|
106
|
+
const names = list ? Array.from({ length: value.length }, (_, index) => String(index)) : Object.keys(value);
|
|
107
|
+
if (!list) spend(budget, 1 + names.length, path);
|
|
108
|
+
Arr.forEach(names, (name) => spendValue(read(value, name, path), [...path, name], depth + 1, budget));
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Decodes the `meta` of a rule with the `meta` Schema of the domain, as an
|
|
112
|
+
* Effect, since the Schema may need an async step. The Schema reads all of
|
|
113
|
+
* the value, so its size counts in the budget.
|
|
114
|
+
*/
|
|
115
|
+
const decodeMeta = ({ input, path }, domain, budget) => Effect.flatMap(attempt(() => typeof input !== "object" || input === null ? void 0 : readOptional(input, "meta", path, () => {
|
|
116
|
+
const value = read(input, "meta", path);
|
|
117
|
+
if (domain.meta === void 0) throw problem([...path, "meta"], "The domain has no Schema for meta");
|
|
118
|
+
spendValue(value, [...path, "meta"], 1, budget);
|
|
119
|
+
return { value };
|
|
120
|
+
})), (found) => found === void 0 ? Effect.void : Effect.mapError(Schema.decodeUnknownEffect(domain.meta)(found.value), (error) => new Problem({
|
|
121
|
+
path: [...path, "meta"],
|
|
122
|
+
message: error.message,
|
|
123
|
+
issue: error.issue
|
|
124
|
+
})));
|
|
125
|
+
const decodeRule = ({ input, path }, meta, budget, domain) => {
|
|
126
|
+
if (typeof input !== "object" || input === null) throw problem(path, "A rule must be an object");
|
|
127
|
+
spend(budget, 1, path);
|
|
128
|
+
const tag = read(input, "_tag", path);
|
|
129
|
+
if (tag !== "Allow" && tag !== "Deny") throw problem([...path, "_tag"], "A rule must be \"Allow\" or \"Deny\"");
|
|
130
|
+
const when = readOptional(input, "when", path, () => decodeAt(read(input, "when", path), [...path, "when"], budget));
|
|
131
|
+
const fields = readOptional(input, "fields", path, () => readStrings(input, "fields", path, budget));
|
|
132
|
+
const reason = readOptional(input, "reason", path, () => readString(input, "reason", path));
|
|
133
|
+
const id = readOptional(input, "id", path, () => readString(input, "id", path));
|
|
134
|
+
return make({
|
|
135
|
+
_tag: tag,
|
|
136
|
+
actions: readStrings(input, "actions", path, budget),
|
|
137
|
+
resources: readStrings(input, "resources", path, budget),
|
|
138
|
+
...when === void 0 ? {} : { when },
|
|
139
|
+
...fields === void 0 ? {} : { fields },
|
|
140
|
+
...reason === void 0 ? {} : { reason },
|
|
141
|
+
...id === void 0 ? {} : { id },
|
|
142
|
+
...meta === void 0 ? {} : { meta },
|
|
143
|
+
domain
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Reads the shape of a tree, and puts each rule in `rules`. The rules are
|
|
148
|
+
* decoded after the walk: a condition has levels of its own, and a decode
|
|
149
|
+
* inside the walk would go down the levels of both.
|
|
150
|
+
*/
|
|
151
|
+
const readTree = (input, path, depth, budget, rules) => {
|
|
152
|
+
if (depth > 256) throw problem(path, `A policy tree has more than ${String(256)} levels. The most is ${String(256)}.`);
|
|
153
|
+
if (typeof input !== "object" || input === null) throw problem(path, "A node of a policy must be an object");
|
|
154
|
+
spend(budget, 1, path);
|
|
155
|
+
const child = (value, at) => readTree(value, at, depth + 1, budget, rules);
|
|
156
|
+
const tag = read(input, "_tag", path);
|
|
157
|
+
switch (tag) {
|
|
158
|
+
case "Rules": {
|
|
159
|
+
const algorithm = readString(input, "algorithm", path);
|
|
160
|
+
if (!algorithms.includes(algorithm)) throw problem([...path, "algorithm"], `"${algorithm}" is not a combining algorithm`);
|
|
161
|
+
const found = Arr.map(readList(input, "rules", path, budget), (rule, index) => rules.push({
|
|
162
|
+
input: rule,
|
|
163
|
+
path: [
|
|
164
|
+
...path,
|
|
165
|
+
"rules",
|
|
166
|
+
index
|
|
167
|
+
]
|
|
168
|
+
}) - 1);
|
|
169
|
+
return sized({
|
|
170
|
+
_tag: "Rules",
|
|
171
|
+
path,
|
|
172
|
+
size: 1 + found.length,
|
|
173
|
+
algorithm,
|
|
174
|
+
rules: found
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
case "All":
|
|
178
|
+
case "Any":
|
|
179
|
+
case "FirstOf": {
|
|
180
|
+
const policies = Arr.map(readList(input, "policies", path, budget), (value, index) => child(value, [
|
|
181
|
+
...path,
|
|
182
|
+
"policies",
|
|
183
|
+
index
|
|
184
|
+
]));
|
|
185
|
+
return sized({
|
|
186
|
+
_tag: tag,
|
|
187
|
+
path,
|
|
188
|
+
size: Arr.reduce(policies, 1, (total, plan) => total + plan.size),
|
|
189
|
+
policies
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
case "Ceiling": {
|
|
193
|
+
const cap = child(read(input, "cap", path), [...path, "cap"]);
|
|
194
|
+
const policy = child(read(input, "policy", path), [...path, "policy"]);
|
|
195
|
+
return sized({
|
|
196
|
+
_tag: "Ceiling",
|
|
197
|
+
path,
|
|
198
|
+
size: 1 + cap.size + policy.size,
|
|
199
|
+
cap,
|
|
200
|
+
policy
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
default: throw problem([...path, "_tag"], "The node has no known tag");
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
/** Stops a plan past `policyNodes`, with the message of the nodes. */
|
|
207
|
+
const sized = (plan) => {
|
|
208
|
+
if (plan.size > 1e5) throw problem(plan.path, `A policy tree has ${String(plan.size)} nodes. The most is ${String(policyNodes)}.`);
|
|
209
|
+
return plan;
|
|
210
|
+
};
|
|
211
|
+
/** Makes the nodes of a plan, with the decoded rules. */
|
|
212
|
+
const build = (plan, rules) => {
|
|
213
|
+
try {
|
|
214
|
+
switch (plan._tag) {
|
|
215
|
+
case "Rules": return new Rules({
|
|
216
|
+
algorithm: plan.algorithm,
|
|
217
|
+
rules: Arr.map(plan.rules, (index) => rules[index])
|
|
218
|
+
});
|
|
219
|
+
case "All":
|
|
220
|
+
case "Any":
|
|
221
|
+
case "FirstOf": return new policy_nodes_exports[plan._tag](Arr.map(plan.policies, (child) => build(child, rules)));
|
|
222
|
+
case "Ceiling": return new Ceiling({
|
|
223
|
+
cap: build(plan.cap, rules),
|
|
224
|
+
policy: build(plan.policy, rules)
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (error instanceof LimitExceeded) throw problem(plan.path, error.message);
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
/** Runs a step that may throw a problem, and puts the problem in the error. */
|
|
233
|
+
const attempt = (step) => Effect.try({
|
|
234
|
+
try: step,
|
|
235
|
+
catch: (error) => {
|
|
236
|
+
if (error instanceof Problem) return error;
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
/**
|
|
241
|
+
* Decodes the JSON form of a policy tree for a domain. It reads the tree
|
|
242
|
+
* first, then the `meta` of each rule as an Effect, then the rules, and it
|
|
243
|
+
* makes the nodes last.
|
|
244
|
+
*/
|
|
245
|
+
const decode = (input, domain) => Effect.gen(function* () {
|
|
246
|
+
const budget = { used: 0 };
|
|
247
|
+
const inputs = [];
|
|
248
|
+
const plan = yield* attempt(() => readTree(input, [], 1, budget, inputs));
|
|
249
|
+
const metas = yield* Effect.forEach(inputs, (rule) => decodeMeta(rule, domain, budget));
|
|
250
|
+
const rules = yield* attempt(() => Arr.map(inputs, (rule, index) => decodeRule(rule, metas[index], budget, domain)));
|
|
251
|
+
return yield* attempt(() => build(plan, rules));
|
|
252
|
+
});
|
|
253
|
+
export { decode, encode, syncMeta, toJson };
|
|
254
|
+
|
|
255
|
+
//# sourceMappingURL=policy-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Json.readList","Json.problem","read","Json.read","Json.Problem","Json.decodeAt","Json.readString","Rule.make","Limits.policyNodes","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling","Limits.LimitExceeded"],"sources":["../../src/internal/policy-json.ts"],"sourcesContent":["/**\n * The JSON form of a policy tree. The decoder is a plain walk, as the decoder\n * of a condition is, and the two share one budget of `jsonNodes` objects and\n * items. It makes each node with its constructor, so the limits of the nodes\n * hold for a decoded tree too. The binding of the rules to the domain comes\n * after, in the Schema of the policy.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport * as Rule from \"./rule.ts\"\nimport * as Json from \"./json.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\n\ntype Node = PolicyNodes.Node\n\n/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */\nexport type MetaJson = (rule: Rule.Rule) => unknown\n\n/**\n * Gives the JSON form of the `meta` of each rule at once, with the `meta`\n * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must\n * not need an async step there.\n */\nexport const syncMeta =\n\t(meta: Schema.Top | undefined): MetaJson =>\n\t(rule) =>\n\t\trule.meta === undefined || meta === undefined\n\t\t\t? undefined\n\t\t\t: Schema.encodeUnknownSync(meta as Schema.Codec<unknown, unknown>)(\n\t\t\t\t\trule.meta,\n\t\t\t\t)\n\n/** The JSON form of the condition of each rule, by the rule. */\ntype WhenJson = (rule: Rule.Rule) => unknown\n\n/** Gives the JSON form of a rule. The domain of a rule is not in it. */\nconst ruleJson = (\n\trule: Rule.Rule,\n\tmetaJson: MetaJson,\n\twhenJson: WhenJson,\n): unknown => {\n\tconst meta = metaJson(rule)\n\treturn {\n\t\t_tag: rule._tag,\n\t\tactions: rule.actions,\n\t\tresources: rule.resources,\n\t\t...(rule.when === undefined ? {} : { when: whenJson(rule) }),\n\t\t...(rule.fields === undefined ? {} : { fields: rule.fields }),\n\t\t...(rule.reason === undefined ? {} : { reason: rule.reason }),\n\t\t...(rule.id === undefined ? {} : { id: rule.id }),\n\t\t// The `meta` of a rule is a decoded value. Its JSON form comes from the\n\t\t// `meta` Schema of the domain.\n\t\t...(meta === undefined ? {} : { meta }),\n\t}\n}\n\n/**\n * Gives the JSON form of a policy tree with no predicate. A condition with a\n * predicate has no JSON form, so it throws an error. The conditions are\n * written first, from a flat list of the rules, so the walk does not go down\n * the levels of the tree and of a condition at once.\n */\nexport const toJson = (node: Node, meta: MetaJson): unknown => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst whens = new Map<Rule.Rule, unknown>(\n\t\tArr.map(PolicyNodes.rulesOf(node), (rule) => [\n\t\t\trule,\n\t\t\trule.when === undefined ? undefined : Json.toJson(rule.when as never),\n\t\t]),\n\t)\n\tconst tree = (current: Node): unknown => {\n\t\tswitch (current._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: current._tag,\n\t\t\t\t\tpolicies: Arr.map(current.policies, tree),\n\t\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn {\n\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\tcap: tree(current.cap),\n\t\t\t\t\tpolicy: tree(current.policy),\n\t\t\t\t}\n\t\t}\n\t}\n\treturn tree(node)\n}\n\n/**\n * Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the\n * domain may need an async step, so each `meta` is encoded as an Effect.\n */\nexport const encode = (\n\tnode: Node,\n\tmeta: Schema.Top | undefined,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n\tEffect.gen(function* () {\n\t\tconst rules = Arr.filter(\n\t\t\tPolicyNodes.rulesOf(node),\n\t\t\t(rule) => rule.meta !== undefined,\n\t\t)\n\t\tconst encoded =\n\t\t\tmeta === undefined\n\t\t\t\t? []\n\t\t\t\t: yield* Effect.forEach(rules, (rule) =>\n\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\tSchema.encodeUnknownEffect(\n\t\t\t\t\t\t\t\tmeta as Schema.Codec<unknown, unknown>,\n\t\t\t\t\t\t\t)(rule.meta),\n\t\t\t\t\t\t\t(json) => [rule, json] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t// A native map, because it must find a rule by the rule itself: two\n\t\t// rules with the same data are `Equal`, and each keeps its own form.\n\t\tconst forms = new Map<Rule.Rule, unknown>(encoded)\n\t\treturn toJson(node, (rule) => forms.get(rule))\n\t})\n\nconst algorithms: ReadonlyArray<string> = [\n\t\"denyOverrides\",\n\t\"permitOverrides\",\n\t\"firstApplicable\",\n\t\"lastApplicable\",\n]\n\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, path, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problem([...path, name, index], \"The item must be a string\")\n\t\t}\n\t\treturn value\n\t})\n\n/**\n * Reads a field that a rule may leave out. A field that the input has only\n * from the prototype of a class is not plain data, so it is a problem: a rule\n * that loses its condition would allow more.\n */\nconst readOptional = <A>(\n\tinput: object,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tread: () => A,\n): A | undefined => {\n\tif (Object.hasOwn(input, name)) {\n\t\treturn read()\n\t}\n\t// A read of the field throws a problem when the field is not data.\n\tJson.read(input, name, path)\n\treturn undefined\n}\n\n/** Counts the objects and items of a value, as the other parts count. */\nconst spendValue = (\n\tvalue: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Json.Budget,\n): void => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn\n\t}\n\tif (depth > Limits.conditionDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`The value has more than ${String(Limits.conditionDepth)} levels. The most is ${String(Limits.conditionDepth)}.`,\n\t\t)\n\t}\n\t// The length of an array counts before its items are read, so a sparse\n\t// array with a very large length stops at once.\n\tconst list = Array.isArray(value)\n\tif (list) {\n\t\tJson.spend(budget, 1 + value.length, path)\n\t}\n\tconst names = list\n\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t: Object.keys(value)\n\tif (!list) {\n\t\tJson.spend(budget, 1 + names.length, path)\n\t}\n\tArr.forEach(names, (name) =>\n\t\tspendValue(\n\t\t\tJson.read(value, name, path),\n\t\t\t[...path, name],\n\t\t\tdepth + 1,\n\t\t\tbudget,\n\t\t),\n\t)\n}\n\n/** A rule in the input, with its place, to decode after the tree. */\ninterface RuleInput {\n\treadonly input: unknown\n\treadonly path: ReadonlyArray<PropertyKey>\n}\n\n/**\n * The shape of a tree, with the place of each rule in a flat list. `size`\n * counts the nodes and the rules, as the nodes do, so a shared input stops\n * at `policyNodes` before the decoder reads all of it.\n */\ntype Plan =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly path: ReadonlyArray<PropertyKey>\n\t\t\treadonly size: number\n\t\t\treadonly algorithm: PolicyNodes.Algorithm\n\t\t\treadonly rules: ReadonlyArray<number>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly path: ReadonlyArray<PropertyKey>\n\t\t\treadonly size: number\n\t\t\treadonly policies: ReadonlyArray<Plan>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly path: ReadonlyArray<PropertyKey>\n\t\t\treadonly size: number\n\t\t\treadonly cap: Plan\n\t\t\treadonly policy: Plan\n\t }\n\n/**\n * Decodes the `meta` of a rule with the `meta` Schema of the domain, as an\n * Effect, since the Schema may need an async step. The Schema reads all of\n * the value, so its size counts in the budget.\n */\nconst decodeMeta = (\n\t{ input, path }: RuleInput,\n\tdomain: Domain.Any,\n\tbudget: Json.Budget,\n): Effect.Effect<unknown, Json.Problem> =>\n\tEffect.flatMap(\n\t\tattempt(() =>\n\t\t\ttypeof input !== \"object\" || input === null\n\t\t\t\t? undefined\n\t\t\t\t: readOptional(input, \"meta\", path, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", path)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problem(\n\t\t\t\t\t\t\t\t[...path, \"meta\"],\n\t\t\t\t\t\t\t\t\"The domain has no Schema for meta\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(value, [...path, \"meta\"], 1, budget)\n\t\t\t\t\t\treturn { value }\n\t\t\t\t\t}),\n\t\t),\n\t\t(found) =>\n\t\t\tfound === undefined\n\t\t\t\t? Effect.void\n\t\t\t\t: Effect.mapError(\n\t\t\t\t\t\tSchema.decodeUnknownEffect(\n\t\t\t\t\t\t\tdomain.meta as Schema.Codec<unknown, unknown>,\n\t\t\t\t\t\t)(found.value),\n\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\tpath: [...path, \"meta\"],\n\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t)\n\nconst decodeRule = (\n\t{ input, path }: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problem([...path, \"_tag\"], 'A rule must be \"Allow\" or \"Deny\"')\n\t}\n\tconst when = readOptional(input, \"when\", path, () =>\n\t\tJson.decodeAt(Json.read(input, \"when\", path), [...path, \"when\"], budget),\n\t)\n\tconst fields = readOptional(input, \"fields\", path, () =>\n\t\treadStrings(input, \"fields\", path, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", path, () =>\n\t\tJson.readString(input, \"reason\", path),\n\t)\n\tconst id = readOptional(input, \"id\", path, () =>\n\t\tJson.readString(input, \"id\", path),\n\t)\n\t// Each rule gets the domain of the Schema, as a rule from the builder of\n\t// the domain has it.\n\treturn Rule.make({\n\t\t_tag: tag,\n\t\tactions: readStrings(input, \"actions\", path, budget),\n\t\tresources: readStrings(input, \"resources\", path, budget),\n\t\t...(when === undefined ? {} : { when: when as never }),\n\t\t...(fields === undefined ? {} : { fields }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t\t...(id === undefined ? {} : { id }),\n\t\t...(meta === undefined ? {} : { meta }),\n\t\tdomain,\n\t})\n}\n\n/**\n * Reads the shape of a tree, and puts each rule in `rules`. The rules are\n * decoded after the walk: a condition has levels of its own, and a decode\n * inside the walk would go down the levels of both.\n */\nconst readTree = (\n\tinput: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): Plan => {\n\tif (depth > Limits.policyDepth) {\n\t\tthrow Json.problem(\n\t\t\tpath,\n\t\t\t`A policy tree has more than ${String(Limits.policyDepth)} levels. The most is ${String(Limits.policyDepth)}.`,\n\t\t)\n\t}\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problem(path, \"A node of a policy must be an object\")\n\t}\n\tJson.spend(budget, 1, path)\n\tconst child = (value: unknown, at: ReadonlyArray<PropertyKey>) =>\n\t\treadTree(value, at, depth + 1, budget, rules)\n\tconst tag = Json.read(input, \"_tag\", path)\n\tswitch (tag) {\n\t\tcase \"Rules\": {\n\t\t\tconst algorithm = Json.readString(input, \"algorithm\", path)\n\t\t\tif (!algorithms.includes(algorithm)) {\n\t\t\t\tthrow Json.problem(\n\t\t\t\t\t[...path, \"algorithm\"],\n\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst found = Arr.map(\n\t\t\t\tJson.readList(input, \"rules\", path, budget),\n\t\t\t\t(rule, index) =>\n\t\t\t\t\trules.push({ input: rule, path: [...path, \"rules\", index] }) - 1,\n\t\t\t)\n\t\t\treturn sized({\n\t\t\t\t_tag: \"Rules\",\n\t\t\t\tpath,\n\t\t\t\tsize: 1 + found.length,\n\t\t\t\talgorithm: algorithm as PolicyNodes.Algorithm,\n\t\t\t\trules: found,\n\t\t\t})\n\t\t}\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\": {\n\t\t\tconst policies = Arr.map(\n\t\t\t\tJson.readList(input, \"policies\", path, budget),\n\t\t\t\t(value, index) => child(value, [...path, \"policies\", index]),\n\t\t\t)\n\t\t\treturn sized({\n\t\t\t\t_tag: tag,\n\t\t\t\tpath,\n\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\tpolicies,\n\t\t\t})\n\t\t}\n\t\tcase \"Ceiling\": {\n\t\t\tconst cap = child(Json.read(input, \"cap\", path), [...path, \"cap\"])\n\t\t\tconst policy = child(Json.read(input, \"policy\", path), [\n\t\t\t\t...path,\n\t\t\t\t\"policy\",\n\t\t\t])\n\t\t\treturn sized({\n\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\tpath,\n\t\t\t\tsize: 1 + cap.size + policy.size,\n\t\t\t\tcap,\n\t\t\t\tpolicy,\n\t\t\t})\n\t\t}\n\t\tdefault:\n\t\t\tthrow Json.problem([...path, \"_tag\"], \"The node has no known tag\")\n\t}\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (plan: Plan): Plan => {\n\tif (plan.size > Limits.policyNodes) {\n\t\tthrow Json.problem(\n\t\t\tplan.path,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(Limits.policyNodes)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/** Makes the nodes of a plan, with the decoded rules. */\nconst build = (plan: Plan, rules: ReadonlyArray<Rule.Rule>): Node => {\n\ttry {\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn new PolicyNodes.Rules({\n\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t})\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn new PolicyNodes[plan._tag](\n\t\t\t\t\tArr.map(plan.policies, (child) => build(child, rules)),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn new PolicyNodes.Ceiling({\n\t\t\t\t\tcap: build(plan.cap, rules),\n\t\t\t\t\tpolicy: build(plan.policy, rules),\n\t\t\t\t})\n\t\t}\n\t} catch (error) {\n\t\t// A node past a limit is a problem at its place.\n\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\tthrow Json.problem(plan.path, error.message)\n\t\t}\n\t\tthrow error\n\t}\n}\n\n/** Runs a step that may throw a problem, and puts the problem in the error. */\nconst attempt = <A>(step: () => A): Effect.Effect<A, Json.Problem> =>\n\tEffect.try({\n\t\ttry: step,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof Json.Problem) {\n\t\t\t\treturn error\n\t\t\t}\n\t\t\tthrow error\n\t\t},\n\t})\n\n/**\n * Decodes the JSON form of a policy tree for a domain. It reads the tree\n * first, then the `meta` of each rule as an Effect, then the rules, and it\n * makes the nodes last.\n */\nexport const decode = (\n\tinput: unknown,\n\tdomain: Domain.Any,\n): Effect.Effect<Node, Json.Problem> =>\n\tEffect.gen(function* () {\n\t\tconst budget: Json.Budget = { used: 0 }\n\t\t// The rules of this decode. The list belongs to this decode.\n\t\tconst inputs: Array<RuleInput> = []\n\t\tconst plan = yield* attempt(() => readTree(input, [], 1, budget, inputs))\n\t\tconst metas = yield* Effect.forEach(inputs, (rule) =>\n\t\t\tdecodeMeta(rule, domain, budget),\n\t\t)\n\t\tconst rules = yield* attempt(() =>\n\t\t\tArr.map(inputs, (rule, index) =>\n\t\t\t\tdecodeRule(rule, metas[index], budget, domain),\n\t\t\t),\n\t\t)\n\t\treturn yield* attempt(() => build(plan, rules))\n\t})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Rule } from "./rule.js";
|
|
2
|
+
import * as Data from "effect/Data";
|
|
3
|
+
export type Algorithm = "denyOverrides" | "permitOverrides" | "firstApplicable" | "lastApplicable";
|
|
4
|
+
interface Facts {
|
|
5
|
+
readonly size: number;
|
|
6
|
+
readonly depth: number;
|
|
7
|
+
readonly opaque: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** The base of every node. It keeps the facts. */
|
|
10
|
+
declare abstract class Base extends Data.Class {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(props: object, facts: Facts);
|
|
13
|
+
/** The number of nodes, with the rules and their conditions. */
|
|
14
|
+
get size(): number;
|
|
15
|
+
/** The number of levels of the tree. */
|
|
16
|
+
get depth(): number;
|
|
17
|
+
/** A rule of the tree has a predicate. */
|
|
18
|
+
get opaque(): boolean;
|
|
19
|
+
}
|
|
20
|
+
/** A list of rules with a combining algorithm. */
|
|
21
|
+
export declare class Rules extends Base {
|
|
22
|
+
readonly _tag = "Rules";
|
|
23
|
+
readonly algorithm: Algorithm;
|
|
24
|
+
readonly rules: ReadonlyArray<Rule>;
|
|
25
|
+
constructor(props: {
|
|
26
|
+
readonly algorithm: Algorithm;
|
|
27
|
+
readonly rules: ReadonlyArray<Rule>;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Every policy must allow, and one deny is enough to deny. */
|
|
31
|
+
export declare class All extends Base {
|
|
32
|
+
readonly _tag = "All";
|
|
33
|
+
readonly policies: ReadonlyArray<Node>;
|
|
34
|
+
constructor(policies: ReadonlyArray<Node>);
|
|
35
|
+
}
|
|
36
|
+
/** One allow is enough. Without an allow, one deny gives a deny. */
|
|
37
|
+
export declare class Any extends Base {
|
|
38
|
+
readonly _tag = "Any";
|
|
39
|
+
readonly policies: ReadonlyArray<Node>;
|
|
40
|
+
constructor(policies: ReadonlyArray<Node>);
|
|
41
|
+
}
|
|
42
|
+
/** The first policy that allows or denies gives the answer. */
|
|
43
|
+
export declare class FirstOf extends Base {
|
|
44
|
+
readonly _tag = "FirstOf";
|
|
45
|
+
readonly policies: ReadonlyArray<Node>;
|
|
46
|
+
constructor(policies: ReadonlyArray<Node>);
|
|
47
|
+
}
|
|
48
|
+
/** A deny from `cap` always wins. An allow from `cap` does nothing. */
|
|
49
|
+
export declare class Ceiling extends Base {
|
|
50
|
+
readonly _tag = "Ceiling";
|
|
51
|
+
readonly cap: Node;
|
|
52
|
+
readonly policy: Node;
|
|
53
|
+
constructor(props: {
|
|
54
|
+
readonly cap: Node;
|
|
55
|
+
readonly policy: Node;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** A node of a policy tree. */
|
|
59
|
+
export type Node = Rules | All | Any | FirstOf | Ceiling;
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=policy-nodes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-nodes.d.ts","names":["Rule.Rule"],"sources":["../../src/internal/policy-nodes.ts"],"mappings":";;YAiBY;UAUF;WACA;WACA;WACA;;;uBAmCK,aAAa,KAAK;;EAGhC,YAAY,eAAe,OAAO;;MAM9B;;MAKA;;MAKA;;;qBAaQ,cAAc;WACjB;WACQ,WAAW;WACX,OAAO,cAAcA;EACtC,YAAY;aACF,WAAW;aACX,OAAO,cAAcA;;;;qBAyCnB,YAAY;WACf;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,YAAY;WACf;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,gBAAgB;WACnB;WACQ,UAAU,cAAc;EACzC,YAAY,UAAU,cAAc;;;qBAOxB,gBAAgB;WACnB;WACQ,KAAK;WACL,QAAQ;EACzB,YAAY;aAAkB,KAAK;aAAe,QAAQ;;;;YAO/C,OAAO,QAAQ,MAAM,MAAM,UAAU"}
|