@nmnmcc/ability 2.0.0 → 2.0.2
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 +5 -3
- package/dist/Condition.d.ts +10 -10
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js.map +1 -1
- package/dist/Policy.d.ts +10 -3
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +11 -4
- package/dist/Policy.js.map +1 -1
- package/dist/internal/authorizer.js +13 -7
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +67 -25
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/policy-json.js +14 -4
- package/dist/internal/policy-json.js.map +1 -1
- package/dist/internal/rule.d.ts.map +1 -1
- package/dist/internal/rule.js +11 -1
- package/dist/internal/rule.js.map +1 -1
- package/package.json +1 -1
- package/src/Condition.ts +18 -25
- package/src/Policy.ts +12 -5
- package/src/internal/authorizer.ts +17 -7
- package/src/internal/bind.ts +111 -36
- package/src/internal/policy-json.ts +13 -10
- package/src/internal/rule.ts +15 -0
package/dist/internal/rule.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isNode } from "./nodes.js";
|
|
1
2
|
import { lookup } from "./catalog.js";
|
|
2
3
|
import { descriptor, isResource } from "./resource.js";
|
|
3
4
|
import { builder as builder$1 } from "../Condition.js";
|
|
@@ -31,6 +32,15 @@ const make = (data) => new Rule({
|
|
|
31
32
|
...data.meta === void 0 ? {} : { meta: data.meta },
|
|
32
33
|
...data.domain === void 0 ? {} : { domain: data.domain }
|
|
33
34
|
});
|
|
35
|
+
/**
|
|
36
|
+
* Makes a rule from its data, as `Policy.rule` does. A condition with a
|
|
37
|
+
* predicate is a defect: the types of the predicate tie it to the kinds of
|
|
38
|
+
* the rule of a builder, and the binding can not check it.
|
|
39
|
+
*/
|
|
40
|
+
const makePlain = (data) => {
|
|
41
|
+
if (data.when !== void 0 && isNode(data.when) && data.when.opaque) throw new Error("Policy.rule takes no condition with a predicate. Make the rule with Policy.builder.");
|
|
42
|
+
return make(data);
|
|
43
|
+
};
|
|
34
44
|
/** Tests if a value is a rule. */
|
|
35
45
|
const isRule = (u) => u instanceof Rule;
|
|
36
46
|
const toArray = (value) => Array.isArray(value) ? value : [value];
|
|
@@ -88,6 +98,6 @@ const matchesTarget = (rule, target) => {
|
|
|
88
98
|
const open = rule._tag === "Deny" && target.descriptor !== void 0 && (lookup(target.descriptor.catalog, field)?.open ?? false);
|
|
89
99
|
return rule.fields.some((pattern) => matchesField(pattern, field) || open && matchesInside(pattern, field));
|
|
90
100
|
};
|
|
91
|
-
export { Rule, builder, isRule, make, matchesField, matchesInside, matchesTarget };
|
|
101
|
+
export { Rule, builder, isRule, make, makePlain, matchesField, matchesInside, matchesTarget };
|
|
92
102
|
|
|
93
103
|
//# sourceMappingURL=rule.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule.js","names":["ResourceData.isResource","ResourceData.descriptor","Condition.builder","Patterns.matches","Patterns.parseBound","Patterns.matchesBelow","Patterns.fitsCatalog","Catalog.lookup"],"sources":["../../src/internal/rule.ts"],"sourcesContent":["/**\n * The values of rules, with no types. The types are in `Policy.ts`.\n *\n * Rules do not change, and two rules with the same data are equal. A rule\n * works in a generator: it gives itself, so `yield* _.allow(...)` gives the\n * rule to `Policy.define`.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Condition from \"../Condition.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as Patterns from \"./patterns.ts\"\n\n/** The data of a rule. */\nexport interface Shape {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Ast\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n\t/**\n\t * The domain of the builder that made the rule. Two domains may have the\n\t * same types but other paths, so the types can not tell them apart. It is\n\t * not in the JSON form of a rule.\n\t */\n\treadonly domain?: Domain.Any\n}\n\n/** The class of every rule value. */\nexport class Rule extends Data.Class<Shape> {\n\t*[Symbol.iterator](): Generator<this, this, unknown> {\n\t\tyield this\n\t\treturn this\n\t}\n}\n\n/** Makes a rule from its data. It does not check the data against a domain. */\nexport const make = (data: Shape): Rule =>\n\t// A missing option is left out, so two rules with the same data are equal.\n\tnew Rule({\n\t\t_tag: data._tag,\n\t\tactions: [...data.actions],\n\t\tresources: [...data.resources],\n\t\t...(data.when === undefined ? {} : { when: data.when }),\n\t\t...(data.fields === undefined ? {} : { fields: [...data.fields] }),\n\t\t...(data.reason === undefined ? {} : { reason: data.reason }),\n\t\t...(data.id === undefined ? {} : { id: data.id }),\n\t\t...(data.meta === undefined ? {} : { meta: data.meta }),\n\t\t...(data.domain === undefined ? {} : { domain: data.domain }),\n\t})\n\n/** Tests if a value is a rule. */\nexport const isRule = (u: unknown): u is Rule => u instanceof Rule\n\nconst toArray = <A>(value: A | ReadonlyArray<A>): ReadonlyArray<A> =>\n\tArray.isArray(value) ? value : [value as A]\n\nconst kindName = (target: unknown): string =>\n\tResourceData.isResource(target)\n\t\t? ResourceData.descriptor(target).kind\n\t\t: (target as string)\n\n/** The options of a rule from a builder, with no types. */\ninterface Options {\n\treadonly when?: (builder: never) => Condition.Ast\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n}\n\nconst fromOptions =\n\t(tag: \"Allow\" | \"Deny\", domain: Domain.Any) =>\n\t(\n\t\taction: string | ReadonlyArray<string>,\n\t\tresource: unknown,\n\t\toptions?: Options,\n\t): Rule =>\n\t\tmake({\n\t\t\t_tag: tag,\n\t\t\tactions: toArray(action),\n\t\t\tresources: Arr.map(toArray(resource), kindName),\n\t\t\t...(options?.when === undefined\n\t\t\t\t? {}\n\t\t\t\t: { when: options.when(Condition.builder() as never) }),\n\t\t\t...(options?.fields === undefined ? {} : { fields: options.fields }),\n\t\t\t...(options?.reason === undefined ? {} : { reason: options.reason }),\n\t\t\t...(options?.id === undefined ? {} : { id: options.id }),\n\t\t\t...(options?.meta === undefined ? {} : { meta: options.meta }),\n\t\t\tdomain,\n\t\t})\n\n/** Makes the rule builder of a domain. */\nexport const builder = (domain: Domain.Any) => ({\n\tallow: fromOptions(\"Allow\", domain),\n\tdeny: fromOptions(\"Deny\", domain),\n})\n\n/**\n * Tests if a field name matches a field name or a field pattern of a rule. A\n * pattern matches the fields below a field that it matches too, so `settings`\n * matches `settings.model`.\n */\nexport const matchesField = (pattern: string, field: string): boolean =>\n\tPatterns.matches(Patterns.parseBound(pattern), field.split(\".\"))\n\n/**\n * Tests if a field pattern may match a field inside `field`, such as\n * `settings.secret` inside `settings`.\n */\nexport const matchesInside = (pattern: string, field: string): boolean =>\n\tPatterns.matchesBelow(Patterns.parseBound(pattern), field.split(\".\"))\n\n/**\n * Tests the parts of a rule that do not need the resource value: the action,\n * the kind and the field.\n *\n * A rule with an empty list of fields covers no request. A rule with fields\n * covers a request with no field only when it is an allow rule. So \"may\n * update some fields\" is a yes. A deny rule for some fields does not stop a\n * request for the whole value. With the `descriptor` of the kind, such an\n * allow rule covers the request only when one of its patterns matches a field\n * of the kind, and a deny rule for a part of a field that holds parts covers\n * a request for the whole field. A grant of fields that the kind does not have is not a grant of\n * some fields.\n */\nexport const matchesTarget = (\n\trule: Shape,\n\ttarget: {\n\t\treadonly action: string\n\t\treadonly kind: string\n\t\treadonly field?: string | undefined\n\t\treadonly descriptor?: Resource.Descriptor | undefined\n\t},\n): boolean => {\n\tif (!rule.actions.includes(\"*\") && !rule.actions.includes(target.action)) {\n\t\treturn false\n\t}\n\tif (!rule.resources.includes(\"*\") && !rule.resources.includes(target.kind)) {\n\t\treturn false\n\t}\n\tif (rule.fields === undefined) {\n\t\treturn true\n\t}\n\t// A rule for no fields is a rule for nothing. A grant with its last field\n\t// taken away must not cover the whole value.\n\tif (rule.fields.length === 0) {\n\t\treturn false\n\t}\n\tif (target.field === undefined) {\n\t\tconst descriptor = target.descriptor\n\t\treturn (\n\t\t\trule._tag === \"Allow\" &&\n\t\t\t(descriptor === undefined ||\n\t\t\t\tArr.some(rule.fields, (pattern) =>\n\t\t\t\t\tPatterns.fitsCatalog(\n\t\t\t\t\t\tPatterns.parseBound(pattern),\n\t\t\t\t\t\tdescriptor.catalog,\n\t\t\t\t\t),\n\t\t\t\t))\n\t\t)\n\t}\n\tconst field = target.field\n\t// A field that holds parts, as a record does, shows its parts too. So a\n\t// deny rule for a part inside it, as `data.secret` inside `data`, also\n\t// covers a request for the whole field.\n\tconst open =\n\t\trule._tag === \"Deny\" &&\n\t\ttarget.descriptor !== undefined &&\n\t\t(Catalog.lookup(target.descriptor.catalog, field)?.open ?? false)\n\treturn rule.fields.some(\n\t\t(pattern) =>\n\t\t\tmatchesField(pattern, field) || (open && matchesInside(pattern, field)),\n\t)\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"rule.js","names":["Nodes.isNode","ResourceData.isResource","ResourceData.descriptor","Condition.builder","Patterns.matches","Patterns.parseBound","Patterns.matchesBelow","Patterns.fitsCatalog","Catalog.lookup"],"sources":["../../src/internal/rule.ts"],"sourcesContent":["/**\n * The values of rules, with no types. The types are in `Policy.ts`.\n *\n * Rules do not change, and two rules with the same data are equal. A rule\n * works in a generator: it gives itself, so `yield* _.allow(...)` gives the\n * rule to `Policy.define`.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Condition from \"../Condition.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as Patterns from \"./patterns.ts\"\n\n/** The data of a rule. */\nexport interface Shape {\n\treadonly _tag: \"Allow\" | \"Deny\"\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: Condition.Ast\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n\t/**\n\t * The domain of the builder that made the rule. Two domains may have the\n\t * same types but other paths, so the types can not tell them apart. It is\n\t * not in the JSON form of a rule.\n\t */\n\treadonly domain?: Domain.Any\n}\n\n/** The class of every rule value. */\nexport class Rule extends Data.Class<Shape> {\n\t*[Symbol.iterator](): Generator<this, this, unknown> {\n\t\tyield this\n\t\treturn this\n\t}\n}\n\n/** Makes a rule from its data. It does not check the data against a domain. */\nexport const make = (data: Shape): Rule =>\n\t// A missing option is left out, so two rules with the same data are equal.\n\tnew Rule({\n\t\t_tag: data._tag,\n\t\tactions: [...data.actions],\n\t\tresources: [...data.resources],\n\t\t...(data.when === undefined ? {} : { when: data.when }),\n\t\t...(data.fields === undefined ? {} : { fields: [...data.fields] }),\n\t\t...(data.reason === undefined ? {} : { reason: data.reason }),\n\t\t...(data.id === undefined ? {} : { id: data.id }),\n\t\t...(data.meta === undefined ? {} : { meta: data.meta }),\n\t\t...(data.domain === undefined ? {} : { domain: data.domain }),\n\t})\n\n/**\n * Makes a rule from its data, as `Policy.rule` does. A condition with a\n * predicate is a defect: the types of the predicate tie it to the kinds of\n * the rule of a builder, and the binding can not check it.\n */\nexport const makePlain = (data: Shape): Rule => {\n\tif (data.when !== undefined && Nodes.isNode(data.when) && data.when.opaque) {\n\t\tthrow new Error(\n\t\t\t\"Policy.rule takes no condition with a predicate. Make the rule with Policy.builder.\",\n\t\t)\n\t}\n\treturn make(data)\n}\n\n/** Tests if a value is a rule. */\nexport const isRule = (u: unknown): u is Rule => u instanceof Rule\n\nconst toArray = <A>(value: A | ReadonlyArray<A>): ReadonlyArray<A> =>\n\tArray.isArray(value) ? value : [value as A]\n\nconst kindName = (target: unknown): string =>\n\tResourceData.isResource(target)\n\t\t? ResourceData.descriptor(target).kind\n\t\t: (target as string)\n\n/** The options of a rule from a builder, with no types. */\ninterface Options {\n\treadonly when?: (builder: never) => Condition.Ast\n\treadonly fields?: ReadonlyArray<string>\n\treadonly reason?: string\n\treadonly id?: string\n\treadonly meta?: unknown\n}\n\nconst fromOptions =\n\t(tag: \"Allow\" | \"Deny\", domain: Domain.Any) =>\n\t(\n\t\taction: string | ReadonlyArray<string>,\n\t\tresource: unknown,\n\t\toptions?: Options,\n\t): Rule =>\n\t\tmake({\n\t\t\t_tag: tag,\n\t\t\tactions: toArray(action),\n\t\t\tresources: Arr.map(toArray(resource), kindName),\n\t\t\t...(options?.when === undefined\n\t\t\t\t? {}\n\t\t\t\t: { when: options.when(Condition.builder() as never) }),\n\t\t\t...(options?.fields === undefined ? {} : { fields: options.fields }),\n\t\t\t...(options?.reason === undefined ? {} : { reason: options.reason }),\n\t\t\t...(options?.id === undefined ? {} : { id: options.id }),\n\t\t\t...(options?.meta === undefined ? {} : { meta: options.meta }),\n\t\t\tdomain,\n\t\t})\n\n/** Makes the rule builder of a domain. */\nexport const builder = (domain: Domain.Any) => ({\n\tallow: fromOptions(\"Allow\", domain),\n\tdeny: fromOptions(\"Deny\", domain),\n})\n\n/**\n * Tests if a field name matches a field name or a field pattern of a rule. A\n * pattern matches the fields below a field that it matches too, so `settings`\n * matches `settings.model`.\n */\nexport const matchesField = (pattern: string, field: string): boolean =>\n\tPatterns.matches(Patterns.parseBound(pattern), field.split(\".\"))\n\n/**\n * Tests if a field pattern may match a field inside `field`, such as\n * `settings.secret` inside `settings`.\n */\nexport const matchesInside = (pattern: string, field: string): boolean =>\n\tPatterns.matchesBelow(Patterns.parseBound(pattern), field.split(\".\"))\n\n/**\n * Tests the parts of a rule that do not need the resource value: the action,\n * the kind and the field.\n *\n * A rule with an empty list of fields covers no request. A rule with fields\n * covers a request with no field only when it is an allow rule. So \"may\n * update some fields\" is a yes. A deny rule for some fields does not stop a\n * request for the whole value. With the `descriptor` of the kind, such an\n * allow rule covers the request only when one of its patterns matches a field\n * of the kind, and a deny rule for a part of a field that holds parts covers\n * a request for the whole field. A grant of fields that the kind does not have is not a grant of\n * some fields.\n */\nexport const matchesTarget = (\n\trule: Shape,\n\ttarget: {\n\t\treadonly action: string\n\t\treadonly kind: string\n\t\treadonly field?: string | undefined\n\t\treadonly descriptor?: Resource.Descriptor | undefined\n\t},\n): boolean => {\n\tif (!rule.actions.includes(\"*\") && !rule.actions.includes(target.action)) {\n\t\treturn false\n\t}\n\tif (!rule.resources.includes(\"*\") && !rule.resources.includes(target.kind)) {\n\t\treturn false\n\t}\n\tif (rule.fields === undefined) {\n\t\treturn true\n\t}\n\t// A rule for no fields is a rule for nothing. A grant with its last field\n\t// taken away must not cover the whole value.\n\tif (rule.fields.length === 0) {\n\t\treturn false\n\t}\n\tif (target.field === undefined) {\n\t\tconst descriptor = target.descriptor\n\t\treturn (\n\t\t\trule._tag === \"Allow\" &&\n\t\t\t(descriptor === undefined ||\n\t\t\t\tArr.some(rule.fields, (pattern) =>\n\t\t\t\t\tPatterns.fitsCatalog(\n\t\t\t\t\t\tPatterns.parseBound(pattern),\n\t\t\t\t\t\tdescriptor.catalog,\n\t\t\t\t\t),\n\t\t\t\t))\n\t\t)\n\t}\n\tconst field = target.field\n\t// A field that holds parts, as a record does, shows its parts too. So a\n\t// deny rule for a part inside it, as `data.secret` inside `data`, also\n\t// covers a request for the whole field.\n\tconst open =\n\t\trule._tag === \"Deny\" &&\n\t\ttarget.descriptor !== undefined &&\n\t\t(Catalog.lookup(target.descriptor.catalog, field)?.open ?? false)\n\treturn rule.fields.some(\n\t\t(pattern) =>\n\t\t\tmatchesField(pattern, field) || (open && matchesInside(pattern, field)),\n\t)\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/Condition.ts
CHANGED
|
@@ -224,32 +224,24 @@ export interface Opaque<out E = never, out R = never> {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
/**
|
|
227
|
-
* The error type of the predicates of a mark `X`. A mark that
|
|
228
|
-
*
|
|
227
|
+
* The error type of the predicates of a mark `X`. A mark that is not an
|
|
228
|
+
* {@link Opaque}, as `unknown` or `object`, may have any error.
|
|
229
229
|
*
|
|
230
230
|
* @stability stable
|
|
231
231
|
* @category utility types
|
|
232
232
|
* @since 2.0.0
|
|
233
233
|
*/
|
|
234
|
-
export type ErrorOf<X> =
|
|
235
|
-
? unknown
|
|
236
|
-
: X extends Opaque<infer E, infer _R>
|
|
237
|
-
? E
|
|
238
|
-
: never
|
|
234
|
+
export type ErrorOf<X> = X extends Opaque<infer E, infer _R> ? E : unknown
|
|
239
235
|
|
|
240
236
|
/**
|
|
241
|
-
* The service type of the predicates of a mark `X`. A mark that
|
|
242
|
-
*
|
|
237
|
+
* The service type of the predicates of a mark `X`. A mark that is not an
|
|
238
|
+
* {@link Opaque}, as `unknown` or `object`, may need any service.
|
|
243
239
|
*
|
|
244
240
|
* @stability stable
|
|
245
241
|
* @category utility types
|
|
246
242
|
* @since 2.0.0
|
|
247
243
|
*/
|
|
248
|
-
export type ServicesOf<X> =
|
|
249
|
-
? unknown
|
|
250
|
-
: X extends Opaque<infer _E, infer R>
|
|
251
|
-
? R
|
|
252
|
-
: never
|
|
244
|
+
export type ServicesOf<X> = X extends Opaque<infer _E, infer R> ? R : unknown
|
|
253
245
|
|
|
254
246
|
/**
|
|
255
247
|
* The mark `X` with no services.
|
|
@@ -258,11 +250,8 @@ export type ServicesOf<X> = unknown extends X
|
|
|
258
250
|
* @category utility types
|
|
259
251
|
* @since 2.0.0
|
|
260
252
|
*/
|
|
261
|
-
export type Provided<X> =
|
|
262
|
-
? Opaque<unknown>
|
|
263
|
-
: X extends Opaque<infer E, infer _R>
|
|
264
|
-
? Opaque<E>
|
|
265
|
-
: never
|
|
253
|
+
export type Provided<X> =
|
|
254
|
+
X extends Opaque<infer E, infer _R> ? Opaque<E> : Opaque<unknown>
|
|
266
255
|
|
|
267
256
|
/**
|
|
268
257
|
* What the predicates of a condition read: a resource value of the type `V`
|
|
@@ -366,14 +355,15 @@ type FieldPathInternal<
|
|
|
366
355
|
> = [Depth] extends [never]
|
|
367
356
|
? never
|
|
368
357
|
: {
|
|
369
|
-
|
|
370
|
-
|
|
358
|
+
// A record with number keys has number keys, as `counts.1`.
|
|
359
|
+
[K in keyof T & (string | number)]-?: PathsOf<
|
|
360
|
+
`${K}`,
|
|
371
361
|
NonNullable<T[K]>,
|
|
372
362
|
Depth,
|
|
373
363
|
Arrays,
|
|
374
364
|
Parents
|
|
375
365
|
>
|
|
376
|
-
}[keyof T & string]
|
|
366
|
+
}[keyof T & (string | number)]
|
|
377
367
|
|
|
378
368
|
/**
|
|
379
369
|
* The paths of one field `K` with the values `V`. Each kind of value in `V`
|
|
@@ -387,8 +377,9 @@ type PathsOf<
|
|
|
387
377
|
Arrays extends boolean,
|
|
388
378
|
Parents extends boolean,
|
|
389
379
|
> =
|
|
390
|
-
// A field that is only ever `null` or missing is a field too
|
|
391
|
-
|
|
380
|
+
// A field that is only ever `null` or missing is a field too, and so is a
|
|
381
|
+
// field that may hold any value, as one of `Schema.Unknown` may.
|
|
382
|
+
| ([V] extends [never] ? K : {} extends V ? K : never)
|
|
392
383
|
| ([Extract<V, Leaf | Whole>] extends [never] ? never : K)
|
|
393
384
|
| ([Extract<V, ReadonlyArray<unknown>>] extends [never]
|
|
394
385
|
? never
|
|
@@ -419,7 +410,9 @@ type ObjectPaths<
|
|
|
419
410
|
: Arrays extends true
|
|
420
411
|
? string extends keyof O
|
|
421
412
|
? K
|
|
422
|
-
:
|
|
413
|
+
: number extends keyof O
|
|
414
|
+
? K
|
|
415
|
+
: never
|
|
423
416
|
: never)
|
|
424
417
|
| `${K}.${FieldPathInternal<O, PreviousDepth[Depth], Arrays, Parents>}`
|
|
425
418
|
|
package/src/Policy.ts
CHANGED
|
@@ -533,14 +533,19 @@ export const builder = <D extends Domain.Any>(domain: D): Builder<D> =>
|
|
|
533
533
|
* against a domain: a policy does that when the rule goes into it. Use
|
|
534
534
|
* {@link builder} for rules with types, or {@link schema} for rules from JSON.
|
|
535
535
|
*
|
|
536
|
+
* The condition may not have a predicate. A policy checks the fields of a
|
|
537
|
+
* condition against the resources of its rule, but it can not check what
|
|
538
|
+
* the code of a predicate reads. A rule from the builder keeps that in its
|
|
539
|
+
* types; this one could give a predicate of one kind to another kind.
|
|
540
|
+
*
|
|
536
541
|
* @stability stable
|
|
537
542
|
* @category constructors
|
|
538
543
|
* @since 2.0.0
|
|
539
544
|
*/
|
|
540
|
-
export const rule
|
|
541
|
-
data: RuleShape<I,
|
|
545
|
+
export const rule: <I = unknown>(
|
|
546
|
+
data: RuleShape<I, never>,
|
|
542
547
|
// The types come from the data only, not from where the rule goes.
|
|
543
|
-
)
|
|
548
|
+
) => Rule<any, NoInfer<I>> = RuleData.makePlain as never
|
|
544
549
|
|
|
545
550
|
/**
|
|
546
551
|
* The options of a list of rules.
|
|
@@ -773,8 +778,10 @@ export type ResidualReads<D, K> = Condition.Reads<
|
|
|
773
778
|
/**
|
|
774
779
|
* Gives the condition on resource values for which the policy allows a
|
|
775
780
|
* request. It has no principal fields and no `Under` nodes, so you can give it
|
|
776
|
-
* to `Condition.fold`. It is
|
|
777
|
-
*
|
|
781
|
+
* to `Condition.fold`. It is made smaller, so it is often `True` or `False`
|
|
782
|
+
* when the answer does not depend on the value, but it does no search: a
|
|
783
|
+
* condition that is true for every value may stay a test. To know if the
|
|
784
|
+
* policy allows every value, or some value, use {@link coverage}.
|
|
778
785
|
*
|
|
779
786
|
* With a scope (`within`), the condition is the scope and the residual
|
|
780
787
|
* together, so it is false for every value outside the scope. Give the policy
|
|
@@ -93,19 +93,29 @@ type AnyTarget = {
|
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* A cache key for a principal. Two keys are equal when they have the same
|
|
96
|
-
* text, so two principals with the same key share one policy.
|
|
97
|
-
*
|
|
98
|
-
*
|
|
96
|
+
* text, so two principals with the same key share one policy. A key from the
|
|
97
|
+
* JSON form also needs equal decoded principals: a Schema may make data when
|
|
98
|
+
* it decodes, as `expired` from a time, so one text may give two principals.
|
|
99
|
+
* The key also keeps the principal, so the cache can load its policy. A plain
|
|
100
|
+
* `Data` value can not do all this, so the key gives its own equality with
|
|
101
|
+
* `Equal.Equal`.
|
|
99
102
|
*/
|
|
100
103
|
class PrincipalKey implements Equal.Equal {
|
|
101
104
|
readonly principal: object
|
|
102
105
|
readonly text: string
|
|
103
|
-
|
|
106
|
+
/** The key is the JSON form, so the decoded principals must be equal too. */
|
|
107
|
+
readonly exact: boolean
|
|
108
|
+
constructor(principal: object, text: string, exact: boolean) {
|
|
104
109
|
this.principal = principal
|
|
105
110
|
this.text = text
|
|
111
|
+
this.exact = exact
|
|
106
112
|
}
|
|
107
113
|
[Equal.symbol](that: unknown): boolean {
|
|
108
|
-
return
|
|
114
|
+
return (
|
|
115
|
+
that instanceof PrincipalKey &&
|
|
116
|
+
that.text === this.text &&
|
|
117
|
+
(!this.exact || Equal.equals(that.principal, this.principal))
|
|
118
|
+
)
|
|
109
119
|
}
|
|
110
120
|
[Hash.symbol](): number {
|
|
111
121
|
return Hash.string(this.text)
|
|
@@ -160,7 +170,7 @@ const jsonKey = (
|
|
|
160
170
|
onFailure: () => undefined,
|
|
161
171
|
onSuccess: (decoded) =>
|
|
162
172
|
Equal.equals(decoded, principal)
|
|
163
|
-
? new PrincipalKey(decoded as object, text)
|
|
173
|
+
? new PrincipalKey(decoded as object, text, true)
|
|
164
174
|
: undefined,
|
|
165
175
|
})
|
|
166
176
|
},
|
|
@@ -226,7 +236,7 @@ export const makeLayer = (
|
|
|
226
236
|
const text = custom(principal)
|
|
227
237
|
return text === undefined
|
|
228
238
|
? undefined
|
|
229
|
-
: new PrincipalKey(canonical?.principal ?? principal, text)
|
|
239
|
+
: new PrincipalKey(canonical?.principal ?? principal, text, false)
|
|
230
240
|
}
|
|
231
241
|
return Layer.effect(
|
|
232
242
|
service,
|
package/src/internal/bind.ts
CHANGED
|
@@ -42,11 +42,17 @@ interface Memo {
|
|
|
42
42
|
readonly suggestions: MutableHashMap.MutableHashMap<string, string>
|
|
43
43
|
/** If a pattern matches a field of a kind, by the kind and the pattern. */
|
|
44
44
|
readonly fits: MutableHashMap.MutableHashMap<string, boolean>
|
|
45
|
+
/** The count of the issues of conditions so far. It belongs to one binding. */
|
|
46
|
+
readonly issues: { count: number; noted: boolean }
|
|
47
|
+
/** The steps of the checks of patterns so far. It belongs to one binding. */
|
|
48
|
+
readonly work: { steps: number; noted: boolean }
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
const makeMemo = (): Memo => ({
|
|
48
52
|
suggestions: MutableHashMap.empty(),
|
|
49
53
|
fits: MutableHashMap.empty(),
|
|
54
|
+
issues: { count: 0, noted: false },
|
|
55
|
+
work: { steps: 0, noted: false },
|
|
50
56
|
})
|
|
51
57
|
|
|
52
58
|
/**
|
|
@@ -183,24 +189,44 @@ const patternIssues = (
|
|
|
183
189
|
): ReadonlyArray<Issue> =>
|
|
184
190
|
Result.match(Patterns.parse(pattern), {
|
|
185
191
|
onFailure: (issue) => [{ path: at, issue }],
|
|
186
|
-
onSuccess: (parsed) =>
|
|
192
|
+
onSuccess: (parsed) => {
|
|
187
193
|
// A rule with no known resource has an issue for that already.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
const fits =
|
|
195
|
+
known.length === 0 ||
|
|
196
|
+
Arr.some(known, (descriptor) => {
|
|
197
|
+
const key = `${descriptor.kind}\u0000${pattern}`
|
|
198
|
+
return Option.getOrElse(MutableHashMap.get(memo.fits, key), () => {
|
|
199
|
+
// Each check of a pattern reads the fields of a catalog. The
|
|
200
|
+
// work of one binding has a limit, so very many patterns stop
|
|
201
|
+
// with one issue, and the policy does not load.
|
|
202
|
+
memo.work.steps +=
|
|
203
|
+
descriptor.catalog.fields.length * Math.max(parsed.length, 1)
|
|
204
|
+
if (memo.work.steps > Limits.searchSteps) {
|
|
205
|
+
return true
|
|
206
|
+
}
|
|
207
|
+
const found = Patterns.fitsCatalog(parsed, descriptor.catalog)
|
|
208
|
+
MutableHashMap.set(memo.fits, key, found)
|
|
209
|
+
return found
|
|
210
|
+
})
|
|
195
211
|
})
|
|
196
|
-
|
|
212
|
+
if (memo.work.steps > Limits.searchSteps && !memo.work.noted) {
|
|
213
|
+
memo.work.noted = true
|
|
214
|
+
return [
|
|
215
|
+
{
|
|
216
|
+
path: at,
|
|
217
|
+
issue: `The field patterns of the policy take more than ${String(Limits.searchSteps)} steps to check. The most is ${String(Limits.searchSteps)}.`,
|
|
218
|
+
},
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
return fits
|
|
197
222
|
? []
|
|
198
223
|
: [
|
|
199
224
|
{
|
|
200
225
|
path: at,
|
|
201
226
|
issue: `The field pattern "${pattern}" matches no field of the resources of the rule`,
|
|
202
227
|
},
|
|
203
|
-
]
|
|
228
|
+
]
|
|
229
|
+
},
|
|
204
230
|
})
|
|
205
231
|
|
|
206
232
|
/** The fields of the resources that a test of the rule may read. */
|
|
@@ -385,6 +411,46 @@ const notWhole = (at: ReadonlyArray<PropertyKey>): Issue => ({
|
|
|
385
411
|
issue: "The condition is not a whole condition tree",
|
|
386
412
|
})
|
|
387
413
|
|
|
414
|
+
/**
|
|
415
|
+
* The most issues that the walk of the conditions of one binding gives. A
|
|
416
|
+
* policy may have very many wrong tests, and each issue keeps its path.
|
|
417
|
+
*/
|
|
418
|
+
const maxIssues = 100
|
|
419
|
+
|
|
420
|
+
/** A path that the walk makes only when it needs it, for an issue. */
|
|
421
|
+
type LazyPath = () => ReadonlyArray<PropertyKey>
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Puts the path of a node before the paths of its issues. A walk keeps no
|
|
425
|
+
* path for a node with no issue, so a deep and wide condition costs little.
|
|
426
|
+
*/
|
|
427
|
+
const under = (
|
|
428
|
+
memo: Memo,
|
|
429
|
+
at: LazyPath,
|
|
430
|
+
issues: ReadonlyArray<Issue>,
|
|
431
|
+
): ReadonlyArray<Issue> => {
|
|
432
|
+
if (issues.length === 0) {
|
|
433
|
+
return issues
|
|
434
|
+
}
|
|
435
|
+
const room = maxIssues - memo.issues.count
|
|
436
|
+
memo.issues.count += issues.length
|
|
437
|
+
const kept = Arr.map(Arr.take(issues, Math.max(room, 0)), (issue) => ({
|
|
438
|
+
path: [...at(), ...issue.path],
|
|
439
|
+
issue: issue.issue,
|
|
440
|
+
}))
|
|
441
|
+
const first = memo.issues.count > maxIssues && !memo.issues.noted
|
|
442
|
+
memo.issues.noted ||= first
|
|
443
|
+
return first
|
|
444
|
+
? [
|
|
445
|
+
...kept,
|
|
446
|
+
{
|
|
447
|
+
path: [],
|
|
448
|
+
issue: `The policy has more problems. The first ${String(maxIssues)} are here.`,
|
|
449
|
+
},
|
|
450
|
+
]
|
|
451
|
+
: kept
|
|
452
|
+
}
|
|
453
|
+
|
|
388
454
|
/**
|
|
389
455
|
* The issues of a condition. The constructors of the nodes checked the shape
|
|
390
456
|
* of each node, so this checks the names and the types against the domain. A
|
|
@@ -395,52 +461,61 @@ const notWhole = (at: ReadonlyArray<PropertyKey>): Issue => ({
|
|
|
395
461
|
const conditionIssues = (
|
|
396
462
|
context: Context,
|
|
397
463
|
node: Nodes.Node,
|
|
398
|
-
at:
|
|
464
|
+
at: LazyPath,
|
|
399
465
|
seen: Set<Nodes.Node>,
|
|
400
466
|
): ReadonlyArray<Issue> => {
|
|
401
467
|
if (seen.has(node)) {
|
|
402
468
|
return []
|
|
403
469
|
}
|
|
404
470
|
seen.add(node)
|
|
471
|
+
const here = (issues: ReadonlyArray<Issue>) => under(context.memo, at, issues)
|
|
405
472
|
switch (node._tag) {
|
|
406
473
|
case "Compare": {
|
|
407
|
-
const exists = fieldIssue(context, node.field, [
|
|
408
|
-
return
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
474
|
+
const exists = fieldIssue(context, node.field, ["field"])
|
|
475
|
+
return here(
|
|
476
|
+
exists.length > 0
|
|
477
|
+
? exists
|
|
478
|
+
: operandIssues(
|
|
479
|
+
context,
|
|
480
|
+
node.field,
|
|
481
|
+
node.op !== "eq" && node.op !== "ne",
|
|
482
|
+
node.value,
|
|
483
|
+
["value"],
|
|
484
|
+
),
|
|
485
|
+
)
|
|
417
486
|
}
|
|
418
487
|
case "In": {
|
|
419
|
-
const exists = fieldIssue(context, node.field, [
|
|
420
|
-
return
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
488
|
+
const exists = fieldIssue(context, node.field, ["field"])
|
|
489
|
+
return here(
|
|
490
|
+
exists.length > 0
|
|
491
|
+
? exists
|
|
492
|
+
: Arr.flatMap(node.values, (operand, index) =>
|
|
493
|
+
operandIssues(context, node.field, false, operand, [
|
|
494
|
+
"values",
|
|
495
|
+
index,
|
|
496
|
+
]),
|
|
497
|
+
),
|
|
498
|
+
)
|
|
429
499
|
}
|
|
430
500
|
case "Exists":
|
|
431
|
-
return fieldIssue(context, node.field, [
|
|
501
|
+
return here(fieldIssue(context, node.field, ["field"]))
|
|
432
502
|
case "Under":
|
|
433
|
-
return underIssues(context, node.prefix,
|
|
503
|
+
return here(underIssues(context, node.prefix, []))
|
|
434
504
|
case "And":
|
|
435
505
|
case "Or":
|
|
436
506
|
return Arr.flatMap(node.conditions, (child, index) =>
|
|
437
|
-
conditionIssues(
|
|
507
|
+
conditionIssues(
|
|
508
|
+
context,
|
|
509
|
+
child,
|
|
510
|
+
() => [...at(), "conditions", index],
|
|
511
|
+
seen,
|
|
512
|
+
),
|
|
438
513
|
)
|
|
439
514
|
case "Not":
|
|
440
515
|
return conditionIssues(
|
|
441
516
|
context,
|
|
442
517
|
node.condition,
|
|
443
|
-
[...at, "condition"],
|
|
518
|
+
() => [...at(), "condition"],
|
|
444
519
|
seen,
|
|
445
520
|
)
|
|
446
521
|
default:
|
|
@@ -529,7 +604,7 @@ export const ruleIssues = (
|
|
|
529
604
|
...(rule.when === undefined
|
|
530
605
|
? []
|
|
531
606
|
: Nodes.isNode(rule.when)
|
|
532
|
-
? conditionIssues(context, rule.when, [...at, "when"], new Set())
|
|
607
|
+
? conditionIssues(context, rule.when, () => [...at, "when"], new Set())
|
|
533
608
|
: [notWhole([...at, "when"])]),
|
|
534
609
|
]
|
|
535
610
|
}
|
|
@@ -16,6 +16,15 @@ import * as PolicyNodes from "./policy-nodes.ts"
|
|
|
16
16
|
|
|
17
17
|
type Node = PolicyNodes.Node
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* The codec of the `meta` Schema of a domain to and from JSON. The JSON form
|
|
21
|
+
* of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so
|
|
22
|
+
* a `Date` in `meta`, for one, becomes a string in it, and a string again
|
|
23
|
+
* when it is decoded.
|
|
24
|
+
*/
|
|
25
|
+
const metaCodec = (meta: Schema.Top): Schema.Codec<unknown, unknown> =>
|
|
26
|
+
Schema.toCodecJson(meta) as unknown as Schema.Codec<unknown, unknown>
|
|
27
|
+
|
|
19
28
|
/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */
|
|
20
29
|
export type MetaJson = (rule: Rule.Rule) => unknown
|
|
21
30
|
|
|
@@ -29,9 +38,7 @@ export const syncMeta =
|
|
|
29
38
|
(rule) =>
|
|
30
39
|
rule.meta === undefined || meta === undefined
|
|
31
40
|
? undefined
|
|
32
|
-
: Schema.encodeUnknownSync(meta
|
|
33
|
-
rule.meta,
|
|
34
|
-
)
|
|
41
|
+
: Schema.encodeUnknownSync(metaCodec(meta))(rule.meta)
|
|
35
42
|
|
|
36
43
|
/** The JSON form of the condition of each rule, by the rule. */
|
|
37
44
|
type WhenJson = (rule: Rule.Rule) => unknown
|
|
@@ -118,9 +125,7 @@ export const encode = (
|
|
|
118
125
|
? []
|
|
119
126
|
: yield* Effect.forEach(rules, (rule) =>
|
|
120
127
|
Effect.map(
|
|
121
|
-
Schema.encodeUnknownEffect(
|
|
122
|
-
meta as Schema.Codec<unknown, unknown>,
|
|
123
|
-
)(rule.meta),
|
|
128
|
+
Schema.encodeUnknownEffect(metaCodec(meta))(rule.meta),
|
|
124
129
|
(json) => [rule, json] as const,
|
|
125
130
|
),
|
|
126
131
|
)
|
|
@@ -264,16 +269,14 @@ const decodeMeta = (
|
|
|
264
269
|
)
|
|
265
270
|
}
|
|
266
271
|
spendValue(value, [...path, "meta"], 1, budget)
|
|
267
|
-
return { value }
|
|
272
|
+
return { value, schema: domain.meta }
|
|
268
273
|
}),
|
|
269
274
|
),
|
|
270
275
|
(found) =>
|
|
271
276
|
found === undefined
|
|
272
277
|
? Effect.void
|
|
273
278
|
: Effect.mapError(
|
|
274
|
-
Schema.decodeUnknownEffect(
|
|
275
|
-
domain.meta as Schema.Codec<unknown, unknown>,
|
|
276
|
-
)(found.value),
|
|
279
|
+
Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value),
|
|
277
280
|
(error) =>
|
|
278
281
|
new Json.Problem({
|
|
279
282
|
path: [...path, "meta"],
|
package/src/internal/rule.ts
CHANGED
|
@@ -11,6 +11,7 @@ import * as Condition from "../Condition.ts"
|
|
|
11
11
|
import type * as Domain from "../Domain.ts"
|
|
12
12
|
import type * as Resource from "../Resource.ts"
|
|
13
13
|
import * as Catalog from "./catalog.ts"
|
|
14
|
+
import * as Nodes from "./nodes.ts"
|
|
14
15
|
import * as ResourceData from "./resource.ts"
|
|
15
16
|
import * as Patterns from "./patterns.ts"
|
|
16
17
|
|
|
@@ -55,6 +56,20 @@ export const make = (data: Shape): Rule =>
|
|
|
55
56
|
...(data.domain === undefined ? {} : { domain: data.domain }),
|
|
56
57
|
})
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Makes a rule from its data, as `Policy.rule` does. A condition with a
|
|
61
|
+
* predicate is a defect: the types of the predicate tie it to the kinds of
|
|
62
|
+
* the rule of a builder, and the binding can not check it.
|
|
63
|
+
*/
|
|
64
|
+
export const makePlain = (data: Shape): Rule => {
|
|
65
|
+
if (data.when !== undefined && Nodes.isNode(data.when) && data.when.opaque) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"Policy.rule takes no condition with a predicate. Make the rule with Policy.builder.",
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
return make(data)
|
|
71
|
+
}
|
|
72
|
+
|
|
58
73
|
/** Tests if a value is a rule. */
|
|
59
74
|
export const isRule = (u: unknown): u is Rule => u instanceof Rule
|
|
60
75
|
|