@o3co/auth.policy-verifier.builtins 0.15.0 → 0.16.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.ja.md +45 -7
- package/README.md +47 -8
- package/dist/collectors/DelegationRangeCollector.d.mts +29 -0
- package/dist/collectors/DelegationRangeCollector.d.mts.map +1 -0
- package/dist/collectors/DelegationRangeCollector.mjs +29 -0
- package/dist/collectors/PayloadClaimAttributeCollector.d.mts +24 -39
- package/dist/collectors/PayloadClaimAttributeCollector.d.mts.map +1 -1
- package/dist/collectors/PayloadClaimAttributeCollector.mjs +26 -41
- package/dist/collectors/PayloadScopeCollector.d.mts +1 -1
- package/dist/collectors/PayloadSubjectIdCollector.d.mts +1 -1
- package/dist/collectors/PayloadSubjectIdCollector.mjs +1 -1
- package/dist/collectors/RequestContextAttributeCollector.d.mts +24 -62
- package/dist/collectors/RequestContextAttributeCollector.d.mts.map +1 -1
- package/dist/collectors/RequestContextAttributeCollector.mjs +33 -74
- package/dist/collectors/StaticPermissionCollector.d.mts +7 -9
- package/dist/collectors/StaticPermissionCollector.d.mts.map +1 -1
- package/dist/collectors/StaticPermissionCollector.mjs +7 -9
- package/dist/collectors/StaticRoleCollector.d.mts +9 -10
- package/dist/collectors/StaticRoleCollector.d.mts.map +1 -1
- package/dist/collectors/StaticRoleCollector.mjs +10 -11
- package/dist/collectors/_attributeMapping.d.mts +1 -0
- package/dist/collectors/_attributeMapping.d.mts.map +1 -1
- package/dist/collectors/_attributeMapping.mjs +3 -0
- package/dist/collectors/_claims.d.mts +2 -3
- package/dist/collectors/_claims.d.mts.map +1 -1
- package/dist/collectors/_claims.mjs +3 -4
- package/dist/delegation/range.d.mts +48 -0
- package/dist/delegation/range.d.mts.map +1 -0
- package/dist/delegation/range.mjs +108 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +7 -0
- package/dist/keys.d.mts +9 -0
- package/dist/keys.d.mts.map +1 -0
- package/dist/keys.mjs +31 -0
- package/dist/module.d.mts.map +1 -1
- package/dist/module.mjs +4 -0
- package/dist/resource/DotNotationResourceParser.d.mts +16 -19
- package/dist/resource/DotNotationResourceParser.d.mts.map +1 -1
- package/dist/resource/DotNotationResourceParser.mjs +20 -25
- package/dist/rules/AttrLiteralCompare.d.mts +3 -5
- package/dist/rules/AttrLiteralCompare.d.mts.map +1 -1
- package/dist/rules/AttrLiteralCompare.mjs +3 -5
- package/dist/rules/AttrLiteralEqual.d.mts +3 -5
- package/dist/rules/AttrLiteralEqual.d.mts.map +1 -1
- package/dist/rules/AttrLiteralEqual.mjs +3 -5
- package/dist/rules/AttrLiteralIn.d.mts +3 -5
- package/dist/rules/AttrLiteralIn.d.mts.map +1 -1
- package/dist/rules/AttrLiteralIn.mjs +3 -5
- package/dist/rules/AttrLiteralNotEqual.d.mts +6 -8
- package/dist/rules/AttrLiteralNotEqual.d.mts.map +1 -1
- package/dist/rules/AttrLiteralNotEqual.mjs +6 -8
- package/dist/rules/AttrLiteralNotIn.d.mts +4 -6
- package/dist/rules/AttrLiteralNotIn.d.mts.map +1 -1
- package/dist/rules/AttrLiteralNotIn.mjs +4 -6
- package/dist/rules/AttrMatchRule.d.mts +1 -1
- package/dist/rules/AttrMatchRule.mjs +1 -1
- package/dist/rules/AttrPairCompare.d.mts +3 -5
- package/dist/rules/AttrPairCompare.d.mts.map +1 -1
- package/dist/rules/AttrPairCompare.mjs +3 -5
- package/dist/rules/AttrPairEqual.d.mts +6 -9
- package/dist/rules/AttrPairEqual.d.mts.map +1 -1
- package/dist/rules/AttrPairEqual.mjs +6 -9
- package/dist/rules/AttrPairNotEqual.d.mts +3 -5
- package/dist/rules/AttrPairNotEqual.d.mts.map +1 -1
- package/dist/rules/AttrPairNotEqual.mjs +3 -5
- package/dist/rules/HasPermission.d.mts +9 -10
- package/dist/rules/HasPermission.d.mts.map +1 -1
- package/dist/rules/HasPermission.mjs +16 -20
- package/dist/rules/HasScope.d.mts +11 -14
- package/dist/rules/HasScope.d.mts.map +1 -1
- package/dist/rules/HasScope.mjs +8 -11
- package/dist/rules/WithinDelegationRange.d.mts +22 -0
- package/dist/rules/WithinDelegationRange.d.mts.map +1 -0
- package/dist/rules/WithinDelegationRange.mjs +38 -0
- package/dist/rules/_sharedValidation.d.mts +12 -19
- package/dist/rules/_sharedValidation.d.mts.map +1 -1
- package/dist/rules/_sharedValidation.mjs +17 -25
- package/dist/rules/collectors/DelegationRangeRuleCollector.d.mts +36 -0
- package/dist/rules/collectors/DelegationRangeRuleCollector.d.mts.map +1 -0
- package/dist/rules/collectors/DelegationRangeRuleCollector.mjs +36 -0
- package/dist/rules/collectors/ResourceActionScopeRuleCollector.d.mts +19 -28
- package/dist/rules/collectors/ResourceActionScopeRuleCollector.d.mts.map +1 -1
- package/dist/rules/collectors/ResourceActionScopeRuleCollector.mjs +21 -30
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttrPairEqual.d.mts","sourceRoot":"","sources":["../../src/rules/AttrPairEqual.mts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,MAAM,WAAW,mBAAmB;IACnC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED
|
|
1
|
+
{"version":3,"file":"AttrPairEqual.d.mts","sourceRoot":"","sources":["../../src/rules/AttrPairEqual.mts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,MAAM,WAAW,mBAAmB;IACnC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,aAAc,YAAW,IAAI;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;gBAEf,MAAM,EAAE,mBAAmB;IAWvC,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;CAO1C"}
|
|
@@ -6,11 +6,10 @@ import { requireAttrName, requireOptionalGroup } from "./_sharedValidation.mjs";
|
|
|
6
6
|
* strings, and are strictly equal. No type coercion is performed: both
|
|
7
7
|
* attribute values must be non-empty strings.
|
|
8
8
|
*
|
|
9
|
-
* ##
|
|
9
|
+
* ## String-only
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Phase 4 AttrMatchRule alias depends on this invariant.
|
|
11
|
+
* The deprecated `AttrMatchRule` inherits verify() from this rule and must stay
|
|
12
|
+
* string-only: do not widen verify() to accept numbers or booleans.
|
|
14
13
|
*
|
|
15
14
|
* ## Grouping and the default ruleType
|
|
16
15
|
*
|
|
@@ -23,11 +22,9 @@ import { requireAttrName, requireOptionalGroup } from "./_sharedValidation.mjs";
|
|
|
23
22
|
*
|
|
24
23
|
* ## Configuration is copied at construction
|
|
25
24
|
*
|
|
26
|
-
* The constructor
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* from the values it validated, and `ruleType` and `message` keep describing
|
|
30
|
-
* them.
|
|
25
|
+
* The constructor validates each field of `config` once and keeps the
|
|
26
|
+
* validated value; the object is not retained, so mutating it afterwards
|
|
27
|
+
* changes neither the rule's answers nor its `ruleType` and `message`.
|
|
31
28
|
*/
|
|
32
29
|
export class AttrPairEqual {
|
|
33
30
|
ruleType;
|
|
@@ -25,11 +25,9 @@ export interface AttrPairNotEqualConfig {
|
|
|
25
25
|
*
|
|
26
26
|
* ## Configuration is copied at construction
|
|
27
27
|
*
|
|
28
|
-
* The constructor
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* from the values it validated, and `ruleType` and `message` keep describing
|
|
32
|
-
* them.
|
|
28
|
+
* The constructor validates each field of `config` once and keeps the
|
|
29
|
+
* validated value; the object is not retained, so mutating it afterwards
|
|
30
|
+
* changes neither the rule's answers nor its `ruleType` and `message`.
|
|
33
31
|
*/
|
|
34
32
|
export declare class AttrPairNotEqual implements Rule {
|
|
35
33
|
readonly ruleType: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttrPairNotEqual.d.mts","sourceRoot":"","sources":["../../src/rules/AttrPairNotEqual.mts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,MAAM,WAAW,sBAAsB;IACtC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED
|
|
1
|
+
{"version":3,"file":"AttrPairNotEqual.d.mts","sourceRoot":"","sources":["../../src/rules/AttrPairNotEqual.mts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,MAAM,WAAW,sBAAsB;IACtC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,gBAAiB,YAAW,IAAI;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,gBAAgB;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;gBAEf,MAAM,EAAE,sBAAsB;IAW1C,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;CAO1C"}
|
|
@@ -22,11 +22,9 @@ import { requireAttrName, requireOptionalGroup } from "./_sharedValidation.mjs";
|
|
|
22
22
|
*
|
|
23
23
|
* ## Configuration is copied at construction
|
|
24
24
|
*
|
|
25
|
-
* The constructor
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* from the values it validated, and `ruleType` and `message` keep describing
|
|
29
|
-
* them.
|
|
25
|
+
* The constructor validates each field of `config` once and keeps the
|
|
26
|
+
* validated value; the object is not retained, so mutating it afterwards
|
|
27
|
+
* changes neither the rule's answers nor its `ruleType` and `message`.
|
|
30
28
|
*/
|
|
31
29
|
export class AttrPairNotEqual {
|
|
32
30
|
ruleType;
|
|
@@ -8,13 +8,11 @@ import type { ReadonlyAttributes, Rule } from "@o3co/auth.policy-verifier.core";
|
|
|
8
8
|
*
|
|
9
9
|
* Comparison is **exact and case-sensitive**, the same philosophy `HasScope`
|
|
10
10
|
* applies to scope values and `DotNotationResourceParser` applies to resource
|
|
11
|
-
* identifiers
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* `
|
|
15
|
-
*
|
|
16
|
-
* namespaces to a scope rule and one to a permission rule. One vocabulary, one
|
|
17
|
-
* matching discipline.
|
|
11
|
+
* identifiers: compare what was written, never a normalized guess at what was
|
|
12
|
+
* meant. `ResourceActionPermissionRuleCollector` builds
|
|
13
|
+
* `{resource.raw}.perm:{action}` from the case-preserving parser, so
|
|
14
|
+
* `Project:1` and `project:1` are two namespaces to a permission rule, as they
|
|
15
|
+
* are to a scope rule.
|
|
18
16
|
*
|
|
19
17
|
* ## Wildcards
|
|
20
18
|
*
|
|
@@ -24,10 +22,11 @@ import type { ReadonlyAttributes, Rule } from "@o3co/auth.policy-verifier.core";
|
|
|
24
22
|
* no-normalization rule: a wildcard is match structure the policy author
|
|
25
23
|
* **wrote into the grant**, not a rewrite of both sides behind their back. The
|
|
26
24
|
* literal halves around the `*` still compare exactly and case-sensitively.
|
|
27
|
-
*
|
|
28
|
-
* silently drop segments
|
|
25
|
+
* A grant with more than one `*` is not read as a pattern (only an exact
|
|
26
|
+
* match passes it), because the two-part split would silently drop segments
|
|
27
|
+
* and over-grant.
|
|
29
28
|
*
|
|
30
|
-
* The two halves must not overlap in the required permission
|
|
29
|
+
* The two halves must not overlap in the required permission: a grant
|
|
31
30
|
* of `"posts.*.read"` does not match `"posts.read"`, where the single `.`
|
|
32
31
|
* would satisfy `startsWith` and `endsWith` at once. The wildcard may match
|
|
33
32
|
* the empty string, but each character of the required permission counts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HasPermission.d.mts","sourceRoot":"","sources":["../../src/rules/HasPermission.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAQ,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGtF
|
|
1
|
+
{"version":3,"file":"HasPermission.d.mts","sourceRoot":"","sources":["../../src/rules/HasPermission.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAQ,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,aAAc,YAAW,IAAI;IAK7B,OAAO,CAAC,UAAU;IAJ9B,QAAQ,CAAC,QAAQ,gBAAgB;IACjC,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEL,UAAU,EAAE,MAAM;IAItC,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;IAoB1C,OAAO,CAAC,KAAK;CAuBb"}
|
|
@@ -10,13 +10,11 @@ import { ATTR_PERMISSIONS, ATTR_ROLES } from "@o3co/auth.policy-verifier.core";
|
|
|
10
10
|
*
|
|
11
11
|
* Comparison is **exact and case-sensitive**, the same philosophy `HasScope`
|
|
12
12
|
* applies to scope values and `DotNotationResourceParser` applies to resource
|
|
13
|
-
* identifiers
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* `
|
|
17
|
-
*
|
|
18
|
-
* namespaces to a scope rule and one to a permission rule. One vocabulary, one
|
|
19
|
-
* matching discipline.
|
|
13
|
+
* identifiers: compare what was written, never a normalized guess at what was
|
|
14
|
+
* meant. `ResourceActionPermissionRuleCollector` builds
|
|
15
|
+
* `{resource.raw}.perm:{action}` from the case-preserving parser, so
|
|
16
|
+
* `Project:1` and `project:1` are two namespaces to a permission rule, as they
|
|
17
|
+
* are to a scope rule.
|
|
20
18
|
*
|
|
21
19
|
* ## Wildcards
|
|
22
20
|
*
|
|
@@ -26,10 +24,11 @@ import { ATTR_PERMISSIONS, ATTR_ROLES } from "@o3co/auth.policy-verifier.core";
|
|
|
26
24
|
* no-normalization rule: a wildcard is match structure the policy author
|
|
27
25
|
* **wrote into the grant**, not a rewrite of both sides behind their back. The
|
|
28
26
|
* literal halves around the `*` still compare exactly and case-sensitively.
|
|
29
|
-
*
|
|
30
|
-
* silently drop segments
|
|
27
|
+
* A grant with more than one `*` is not read as a pattern (only an exact
|
|
28
|
+
* match passes it), because the two-part split would silently drop segments
|
|
29
|
+
* and over-grant.
|
|
31
30
|
*
|
|
32
|
-
* The two halves must not overlap in the required permission
|
|
31
|
+
* The two halves must not overlap in the required permission: a grant
|
|
33
32
|
* of `"posts.*.read"` does not match `"posts.read"`, where the single `.`
|
|
34
33
|
* would satisfy `startsWith` and `endsWith` at once. The wildcard may match
|
|
35
34
|
* the empty string, but each character of the required permission counts
|
|
@@ -45,7 +44,7 @@ export class HasPermission {
|
|
|
45
44
|
this.message = `User does not have required permission: ${permission}`;
|
|
46
45
|
}
|
|
47
46
|
verify(attrs) {
|
|
48
|
-
// Nothing about these values is taken on the cast's word
|
|
47
|
+
// Nothing about these values is taken on the cast's word: both
|
|
49
48
|
// arrive from collectors, and a store-backed one can hand back anything
|
|
50
49
|
// — a non-array under either key, a role whose `permissions` is missing
|
|
51
50
|
// or not an array, non-string entries. Every malformed shape is ignored
|
|
@@ -57,7 +56,7 @@ export class HasPermission {
|
|
|
57
56
|
const roles = attrs.get(ATTR_ROLES);
|
|
58
57
|
const fromRoles = (Array.isArray(roles) ? roles : []).flatMap((role) => Array.isArray(role?.permissions) ? role.permissions : []);
|
|
59
58
|
const all = [...(Array.isArray(direct) ? direct : []), ...fromRoles];
|
|
60
|
-
// Non-string entries never match and never throw, as in HasScope
|
|
59
|
+
// Non-string entries never match and never throw, as in HasScope.
|
|
61
60
|
return all.some((p) => typeof p === "string" && this.match(p, this.permission));
|
|
62
61
|
}
|
|
63
62
|
match(permission, required) {
|
|
@@ -66,17 +65,14 @@ export class HasPermission {
|
|
|
66
65
|
if (permission === required)
|
|
67
66
|
return true;
|
|
68
67
|
if (permission.includes("*")) {
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// "b"), which can grant broader access than intended. Returning false is the
|
|
74
|
-
// safe default: deny rather than over-grant. Single-wildcard permissions
|
|
75
|
-
// (e.g. "posts.*" or "*.read") continue to work as before.
|
|
68
|
+
// A grant with more than one wildcard is not read as a pattern; only
|
|
69
|
+
// the exact match above passes it. The two-part split below would drop
|
|
70
|
+
// everything after the second segment ("a*c*b" read as "a*c") and
|
|
71
|
+
// over-grant.
|
|
76
72
|
if ((permission.match(/\*/g) ?? []).length > 1)
|
|
77
73
|
return false;
|
|
78
74
|
const [prefix, suffix] = permission.split("*");
|
|
79
|
-
// The halves must not overlap in `required
|
|
75
|
+
// The halves must not overlap in `required`: "posts.*.read"
|
|
80
76
|
// must not match "posts.read", where the single "." satisfies both
|
|
81
77
|
// startsWith and endsWith. Length is the whole check — the wildcard
|
|
82
78
|
// may match the empty string, but a character may only count toward
|
|
@@ -5,10 +5,10 @@ export interface HasScopeOptions {
|
|
|
5
5
|
* Opt in to treating a bare granted scope (one containing no `:`) as
|
|
6
6
|
* `read:<scope>` in addition to its literal value. Defaults to `false`.
|
|
7
7
|
*
|
|
8
|
-
* This exists only for deployments whose issuer emits bare resource names
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* This exists only for deployments whose issuer emits bare resource names.
|
|
9
|
+
* It is off by default because the rewrite invents an action the issuer
|
|
10
|
+
* never wrote: a token granted `project` is silently promoted to
|
|
11
|
+
* `read:project`.
|
|
12
12
|
*/
|
|
13
13
|
allowBareScopeRewrite?: boolean;
|
|
14
14
|
}
|
|
@@ -20,21 +20,18 @@ export interface HasScopeOptions {
|
|
|
20
20
|
* Comparison is an **exact, case-sensitive string equality** against each value
|
|
21
21
|
* in `ATTR_SCOPES`. OAuth 2.0 scope values are case-sensitive opaque strings
|
|
22
22
|
* (RFC 6749 §3.3), so the verifier compares what the issuer wrote rather than a
|
|
23
|
-
* normalized form of it:
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* `read:project`, and `read:project` does not satisfy it. Nothing is split
|
|
28
|
-
* off at the second `:`; a scope the issuer deliberately narrowed must not
|
|
29
|
-
* collapse into the broader one.
|
|
23
|
+
* normalized form of it: `read:PROJECT` does **not** satisfy `read:project`,
|
|
24
|
+
* and `read:project:restricted` neither satisfies `read:project` nor is
|
|
25
|
+
* satisfied by it — a scope the issuer deliberately narrowed must not collapse
|
|
26
|
+
* into the broader one.
|
|
30
27
|
*
|
|
31
28
|
* ## Bare-scope rewrite
|
|
32
29
|
*
|
|
33
30
|
* A granted scope carrying no `:` is compared literally unless
|
|
34
31
|
* `{ allowBareScopeRewrite: true }` is passed, in which case it also matches
|
|
35
|
-
* `read:<scope>`.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
32
|
+
* `read:<scope>`. A scope containing `:`, such as `project:restricted`, is
|
|
33
|
+
* never rewritten: which of its segments is the action is unknowable, and
|
|
34
|
+
* guessing would over-grant.
|
|
38
35
|
*
|
|
39
36
|
* Non-string entries in `ATTR_SCOPES` never match and never throw: the map is
|
|
40
37
|
* untyped, and a malformed value must produce a denial, not a crash.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HasScope.d.mts","sourceRoot":"","sources":["../../src/rules/HasScope.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC/B;;;;;;;;OAQG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED
|
|
1
|
+
{"version":3,"file":"HasScope.d.mts","sourceRoot":"","sources":["../../src/rules/HasScope.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC/B;;;;;;;;OAQG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,QAAS,YAAW,IAAI;IAQnC,OAAO,CAAC,KAAK;IAPd,QAAQ,CAAC,QAAQ,WAAW;IAC5B,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAU;gBAGvC,KAAK,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,eAAe;IAM1B,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;IAM1C,OAAO,CAAC,UAAU;CAYlB"}
|
package/dist/rules/HasScope.mjs
CHANGED
|
@@ -9,21 +9,18 @@ import { ATTR_SCOPES } from "@o3co/auth.policy-verifier.core";
|
|
|
9
9
|
* Comparison is an **exact, case-sensitive string equality** against each value
|
|
10
10
|
* in `ATTR_SCOPES`. OAuth 2.0 scope values are case-sensitive opaque strings
|
|
11
11
|
* (RFC 6749 §3.3), so the verifier compares what the issuer wrote rather than a
|
|
12
|
-
* normalized form of it:
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* `read:project`, and `read:project` does not satisfy it. Nothing is split
|
|
17
|
-
* off at the second `:`; a scope the issuer deliberately narrowed must not
|
|
18
|
-
* collapse into the broader one.
|
|
12
|
+
* normalized form of it: `read:PROJECT` does **not** satisfy `read:project`,
|
|
13
|
+
* and `read:project:restricted` neither satisfies `read:project` nor is
|
|
14
|
+
* satisfied by it — a scope the issuer deliberately narrowed must not collapse
|
|
15
|
+
* into the broader one.
|
|
19
16
|
*
|
|
20
17
|
* ## Bare-scope rewrite
|
|
21
18
|
*
|
|
22
19
|
* A granted scope carrying no `:` is compared literally unless
|
|
23
20
|
* `{ allowBareScopeRewrite: true }` is passed, in which case it also matches
|
|
24
|
-
* `read:<scope>`.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
21
|
+
* `read:<scope>`. A scope containing `:`, such as `project:restricted`, is
|
|
22
|
+
* never rewritten: which of its segments is the action is unknowable, and
|
|
23
|
+
* guessing would over-grant.
|
|
27
24
|
*
|
|
28
25
|
* Non-string entries in `ATTR_SCOPES` never match and never throw: the map is
|
|
29
26
|
* untyped, and a malformed value must produce a denial, not a crash.
|
|
@@ -50,7 +47,7 @@ export class HasScope {
|
|
|
50
47
|
return true;
|
|
51
48
|
// The rewrite applies to bare scopes only. A granted scope that already
|
|
52
49
|
// contains ":" is never re-interpreted: splitting it to guess an action
|
|
53
|
-
//
|
|
50
|
+
// would let "read:project:restricted" pass as "read:project".
|
|
54
51
|
if (this.allowBareScopeRewrite && !granted.includes(":")) {
|
|
55
52
|
return `read:${granted}` === required;
|
|
56
53
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReadonlyAttributes, Rule } from "@o3co/auth.policy-verifier.core";
|
|
2
|
+
/**
|
|
3
|
+
* Rule that passes when an entry of `ATTR_DELEGATION_RANGE` contains the
|
|
4
|
+
* requested path, by the claims contract's containment rule (`rangeContains`).
|
|
5
|
+
* A range that is absent, not a list, or holds no string that contains the
|
|
6
|
+
* path fails, and so does a requested path outside the grammar, or none at
|
|
7
|
+
* all — `null`, a request whose action is not one action of the grammar
|
|
8
|
+
* (`requestedRangePath`). The rule restricts: it narrows what the policies
|
|
9
|
+
* allow and is no reason to allow, so a request no policy rule applies to goes
|
|
10
|
+
* to `onEmptyRuleSet` and is never allowed by the range alone (under
|
|
11
|
+
* `"allow"` it is still held to it).
|
|
12
|
+
*/
|
|
13
|
+
export declare class WithinDelegationRange implements Rule {
|
|
14
|
+
private readonly path;
|
|
15
|
+
readonly ruleType = "delegation_range";
|
|
16
|
+
readonly code = "outside_delegation_range";
|
|
17
|
+
readonly restricts = true;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
constructor(path: string | null);
|
|
20
|
+
verify(attrs: ReadonlyAttributes): boolean;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=WithinDelegationRange.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WithinDelegationRange.d.mts","sourceRoot":"","sources":["../../src/rules/WithinDelegationRange.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAIhF;;;;;;;;;;GAUG;AACH,qBAAa,qBAAsB,YAAW,IAAI;IAMrC,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,QAAQ,CAAC,QAAQ,sBAAsB;IACvC,QAAQ,CAAC,IAAI,8BAA8B;IAC3C,QAAQ,CAAC,SAAS,QAAQ;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEI,IAAI,EAAE,MAAM,GAAG,IAAI;IAOhD,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;CAO1C"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { rangeContains } from "../delegation/range.mjs";
|
|
4
|
+
import { ATTR_DELEGATION_RANGE } from "../keys.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* Rule that passes when an entry of `ATTR_DELEGATION_RANGE` contains the
|
|
7
|
+
* requested path, by the claims contract's containment rule (`rangeContains`).
|
|
8
|
+
* A range that is absent, not a list, or holds no string that contains the
|
|
9
|
+
* path fails, and so does a requested path outside the grammar, or none at
|
|
10
|
+
* all — `null`, a request whose action is not one action of the grammar
|
|
11
|
+
* (`requestedRangePath`). The rule restricts: it narrows what the policies
|
|
12
|
+
* allow and is no reason to allow, so a request no policy rule applies to goes
|
|
13
|
+
* to `onEmptyRuleSet` and is never allowed by the range alone (under
|
|
14
|
+
* `"allow"` it is still held to it).
|
|
15
|
+
*/
|
|
16
|
+
export class WithinDelegationRange {
|
|
17
|
+
path;
|
|
18
|
+
ruleType = "delegation_range";
|
|
19
|
+
code = "outside_delegation_range";
|
|
20
|
+
restricts = true;
|
|
21
|
+
message;
|
|
22
|
+
constructor(path) {
|
|
23
|
+
this.path = path;
|
|
24
|
+
this.message =
|
|
25
|
+
path === null
|
|
26
|
+
? "Request is outside the token's delegation range: its action is not one action of the range grammar"
|
|
27
|
+
: `Request is outside the token's delegation range: ${path}`;
|
|
28
|
+
}
|
|
29
|
+
verify(attrs) {
|
|
30
|
+
const path = this.path;
|
|
31
|
+
if (path === null)
|
|
32
|
+
return false;
|
|
33
|
+
const range = attrs.get(ATTR_DELEGATION_RANGE);
|
|
34
|
+
if (!Array.isArray(range))
|
|
35
|
+
return false;
|
|
36
|
+
return range.some((entry) => typeof entry === "string" && rangeContains(entry, path));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -4,11 +4,9 @@ export type CompareOp = "lt" | "le" | "gt" | "ge";
|
|
|
4
4
|
export declare const COMPARE_OPS: readonly CompareOp[];
|
|
5
5
|
/**
|
|
6
6
|
* Validates that a config field used as an attribute name is a non-empty string
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `
|
|
10
|
-
* `attr_pair_equal:x:y:z`), which the evaluator would silently OR together
|
|
11
|
-
* and weaken authorization.
|
|
7
|
+
* without `:`, the separator of derived `ruleType` strings. With `:` allowed,
|
|
8
|
+
* `(a="x:y", b="z")` and `(a="x", b="y:z")` would both produce
|
|
9
|
+
* `attr_pair_equal:x:y:z`, which the evaluator would silently OR together.
|
|
12
10
|
* @param className - The rule class name, used in error messages.
|
|
13
11
|
* @param fieldName - The config field name ("a" or "b"), used in error messages.
|
|
14
12
|
* @param value - The raw value to validate.
|
|
@@ -18,10 +16,9 @@ export declare const COMPARE_OPS: readonly CompareOp[];
|
|
|
18
16
|
export declare function requireAttrName(className: string, fieldName: "a" | "b", value: unknown): string;
|
|
19
17
|
/**
|
|
20
18
|
* Validates that a config field used as a literal comparison value is a
|
|
21
|
-
* string, number, or boolean (i.e. a LiteralValue). NaN is rejected
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* would always pass against any numeric attribute, weakening authorization.
|
|
19
|
+
* string, number, or boolean (i.e. a LiteralValue). NaN is rejected: `x !== NaN`
|
|
20
|
+
* is always true and `x === NaN` always false, so a NotEqual(v=NaN) would pass
|
|
21
|
+
* against any numeric attribute and an Equal(v=NaN) against none.
|
|
25
22
|
* @param className - The rule class name, used in error messages.
|
|
26
23
|
* @param fieldName - The config field name ("v"), used in error messages.
|
|
27
24
|
* @param value - The raw value to validate.
|
|
@@ -67,20 +64,16 @@ export declare function requireCompareOp(className: string, value: unknown): Com
|
|
|
67
64
|
export declare function requireOptionalGroup(className: string, value: unknown): string | undefined;
|
|
68
65
|
/**
|
|
69
66
|
* Computes a stable cache/dedup key for an array of LiteralValues.
|
|
70
|
-
* Format: `{type}:{count}:{hashPrefix}` — `hashPrefix` is a
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* behavior, so they must not change its `ruleType`), then applies
|
|
67
|
+
* Format: `{type}:{count}:{hashPrefix}` — `hashPrefix` is `fnv1a64` over a
|
|
68
|
+
* canonical serialization of `values`. The canonical form first deduplicates
|
|
69
|
+
* `values` (AttrLiteralIn / AttrLiteralNotIn test membership through a `Set`,
|
|
70
|
+
* so duplicates must not change the `ruleType`), then applies
|
|
75
71
|
* `JSON.stringify(String(v))` to each remaining element before sorting and
|
|
76
72
|
* joining with `,`. The per-element quoting prevents separator collisions
|
|
77
73
|
* across different arrays (e.g. `["a,b", "c"]` vs `["a", "b,c"]`).
|
|
78
|
-
*
|
|
74
|
+
* `{count}` is the post-dedup element count.
|
|
79
75
|
* Caller must guarantee values is a non-empty homogeneous LiteralValue[].
|
|
80
|
-
*
|
|
81
|
-
* The hash is non-cryptographic by design, but uses 64-bit output to keep
|
|
82
|
-
* the birthday-collision bound (~2^32) far above any realistic policy size
|
|
83
|
-
* and to resist deliberate collision construction.
|
|
76
|
+
* The hash is non-cryptographic by design (see `fnv1a64`).
|
|
84
77
|
*/
|
|
85
78
|
export declare function computeValuesKey(values: LiteralValue[]): string;
|
|
86
79
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_sharedValidation.d.mts","sourceRoot":"","sources":["../../src/rules/_sharedValidation.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"_sharedValidation.d.mts","sourceRoot":"","sources":["../../src/rules/_sharedValidation.mts"],"names":[],"mappings":"AA2BA,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAOzC;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAarD,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAA6B,CAAC;AAE1E;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAe/F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,OAAO,GACZ,YAAY,CAad;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAQvF;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY,EAAE,CAkChG;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,CAU7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAe1F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CAQ/D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAWxF"}
|
|
@@ -9,12 +9,11 @@
|
|
|
9
9
|
* lowercase hex characters, zero-padded.
|
|
10
10
|
*
|
|
11
11
|
* Used by `computeValuesKey` to build a stable grouping suffix for `ruleType`.
|
|
12
|
-
* The 64-bit output
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* grouping when two distinct rules on the same attribute share a `ruleType`.)
|
|
12
|
+
* The 64-bit output puts the birthday-collision bound at ~2^32, far above any
|
|
13
|
+
* realistic policy size, so distinct value sets do not collide by accident.
|
|
14
|
+
* It is not collision-resistant: a birthday search of about 2^32 hashes,
|
|
15
|
+
* within reach of commodity hardware, finds one on purpose, and two distinct
|
|
16
|
+
* rules on the same attribute that share a `ruleType` are OR-combined.
|
|
18
17
|
*
|
|
19
18
|
* Iterates UTF-16 code units via `charCodeAt`. BigInt is used because
|
|
20
19
|
* 64-bit multiplication overflows IEEE-754 doubles; `Math.imul` only
|
|
@@ -47,11 +46,9 @@ function describeType(value) {
|
|
|
47
46
|
export const COMPARE_OPS = ["lt", "le", "gt", "ge"];
|
|
48
47
|
/**
|
|
49
48
|
* Validates that a config field used as an attribute name is a non-empty string
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* `
|
|
53
|
-
* `attr_pair_equal:x:y:z`), which the evaluator would silently OR together
|
|
54
|
-
* and weaken authorization.
|
|
49
|
+
* without `:`, the separator of derived `ruleType` strings. With `:` allowed,
|
|
50
|
+
* `(a="x:y", b="z")` and `(a="x", b="y:z")` would both produce
|
|
51
|
+
* `attr_pair_equal:x:y:z`, which the evaluator would silently OR together.
|
|
55
52
|
* @param className - The rule class name, used in error messages.
|
|
56
53
|
* @param fieldName - The config field name ("a" or "b"), used in error messages.
|
|
57
54
|
* @param value - The raw value to validate.
|
|
@@ -72,10 +69,9 @@ export function requireAttrName(className, fieldName, value) {
|
|
|
72
69
|
}
|
|
73
70
|
/**
|
|
74
71
|
* Validates that a config field used as a literal comparison value is a
|
|
75
|
-
* string, number, or boolean (i.e. a LiteralValue). NaN is rejected
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* would always pass against any numeric attribute, weakening authorization.
|
|
72
|
+
* string, number, or boolean (i.e. a LiteralValue). NaN is rejected: `x !== NaN`
|
|
73
|
+
* is always true and `x === NaN` always false, so a NotEqual(v=NaN) would pass
|
|
74
|
+
* against any numeric attribute and an Equal(v=NaN) against none.
|
|
79
75
|
* @param className - The rule class name, used in error messages.
|
|
80
76
|
* @param fieldName - The config field name ("v"), used in error messages.
|
|
81
77
|
* @param value - The raw value to validate.
|
|
@@ -185,20 +181,16 @@ export function requireOptionalGroup(className, value) {
|
|
|
185
181
|
}
|
|
186
182
|
/**
|
|
187
183
|
* Computes a stable cache/dedup key for an array of LiteralValues.
|
|
188
|
-
* Format: `{type}:{count}:{hashPrefix}` — `hashPrefix` is a
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
* behavior, so they must not change its `ruleType`), then applies
|
|
184
|
+
* Format: `{type}:{count}:{hashPrefix}` — `hashPrefix` is `fnv1a64` over a
|
|
185
|
+
* canonical serialization of `values`. The canonical form first deduplicates
|
|
186
|
+
* `values` (AttrLiteralIn / AttrLiteralNotIn test membership through a `Set`,
|
|
187
|
+
* so duplicates must not change the `ruleType`), then applies
|
|
193
188
|
* `JSON.stringify(String(v))` to each remaining element before sorting and
|
|
194
189
|
* joining with `,`. The per-element quoting prevents separator collisions
|
|
195
190
|
* across different arrays (e.g. `["a,b", "c"]` vs `["a", "b,c"]`).
|
|
196
|
-
*
|
|
191
|
+
* `{count}` is the post-dedup element count.
|
|
197
192
|
* Caller must guarantee values is a non-empty homogeneous LiteralValue[].
|
|
198
|
-
*
|
|
199
|
-
* The hash is non-cryptographic by design, but uses 64-bit output to keep
|
|
200
|
-
* the birthday-collision bound (~2^32) far above any realistic policy size
|
|
201
|
-
* and to resist deliberate collision construction.
|
|
193
|
+
* The hash is non-cryptographic by design (see `fnv1a64`).
|
|
202
194
|
*/
|
|
203
195
|
export function computeValuesKey(values) {
|
|
204
196
|
const elementType = typeof values[0];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CollectorContext, Rule, RuleCollector } from "@o3co/auth.policy-verifier.core";
|
|
2
|
+
/** Config entry accepted by `DelegationRangeRuleCollector`. */
|
|
3
|
+
export interface DelegationRangeRuleCollectorConfig {
|
|
4
|
+
/**
|
|
5
|
+
* The `authorization_details` type the delegation range is carried under.
|
|
6
|
+
* Required — the same value `DelegationRangeCollector` is given.
|
|
7
|
+
*/
|
|
8
|
+
type: string;
|
|
9
|
+
/** The claim the entries are read from. Defaults to `authorization_details`. */
|
|
10
|
+
claim?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Emits one `WithinDelegationRange` rule for the requested path — the raw
|
|
14
|
+
* resource, then the action, joined by `.` (`requestedRangePath`, which
|
|
15
|
+
* refuses an action that is not one action of the grammar, so the rule
|
|
16
|
+
* fails) — when the token carries a range (`entriesOfType`: an entry of the
|
|
17
|
+
* configured type, or the claim in another shape), and none otherwise.
|
|
18
|
+
* The rule is a group of its own and restricts, so it is decided together
|
|
19
|
+
* with the policies and never instead of them: a delegated token is allowed
|
|
20
|
+
* only what both allow, one no policy rule applies to goes to
|
|
21
|
+
* `onEmptyRuleSet`, and a token without a range is decided as it would be
|
|
22
|
+
* without this collector.
|
|
23
|
+
*
|
|
24
|
+
* The range itself is read by `DelegationRangeCollector`, which must be
|
|
25
|
+
* configured with the same `type` and `claim`; nothing checks that they agree.
|
|
26
|
+
* Where only this collector finds the token's entries, the rule finds no
|
|
27
|
+
* range and the request is denied; where only that one does, no rule is
|
|
28
|
+
* emitted and the range is not enforced.
|
|
29
|
+
*/
|
|
30
|
+
export declare class DelegationRangeRuleCollector implements RuleCollector {
|
|
31
|
+
private readonly type;
|
|
32
|
+
private readonly claim;
|
|
33
|
+
constructor(config: DelegationRangeRuleCollectorConfig);
|
|
34
|
+
collect(context: CollectorContext): Promise<Rule[]>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=DelegationRangeRuleCollector.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DelegationRangeRuleCollector.d.mts","sourceRoot":"","sources":["../../../src/rules/collectors/DelegationRangeRuleCollector.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAU7F,+DAA+D;AAC/D,MAAM,WAAW,kCAAkC;IAClD;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,4BAA6B,YAAW,aAAa;IACjE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,MAAM,EAAE,kCAAkC;IAShD,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;CAIzD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { resolveClaimName } from "../../collectors/_claims.mjs";
|
|
4
|
+
import { DEFAULT_AUTHORIZATION_DETAILS_CLAIM, entriesOfType, requestedRangePath, resolveRangeType, } from "../../delegation/range.mjs";
|
|
5
|
+
import { WithinDelegationRange } from "../WithinDelegationRange.mjs";
|
|
6
|
+
/**
|
|
7
|
+
* Emits one `WithinDelegationRange` rule for the requested path — the raw
|
|
8
|
+
* resource, then the action, joined by `.` (`requestedRangePath`, which
|
|
9
|
+
* refuses an action that is not one action of the grammar, so the rule
|
|
10
|
+
* fails) — when the token carries a range (`entriesOfType`: an entry of the
|
|
11
|
+
* configured type, or the claim in another shape), and none otherwise.
|
|
12
|
+
* The rule is a group of its own and restricts, so it is decided together
|
|
13
|
+
* with the policies and never instead of them: a delegated token is allowed
|
|
14
|
+
* only what both allow, one no policy rule applies to goes to
|
|
15
|
+
* `onEmptyRuleSet`, and a token without a range is decided as it would be
|
|
16
|
+
* without this collector.
|
|
17
|
+
*
|
|
18
|
+
* The range itself is read by `DelegationRangeCollector`, which must be
|
|
19
|
+
* configured with the same `type` and `claim`; nothing checks that they agree.
|
|
20
|
+
* Where only this collector finds the token's entries, the rule finds no
|
|
21
|
+
* range and the request is denied; where only that one does, no rule is
|
|
22
|
+
* emitted and the range is not enforced.
|
|
23
|
+
*/
|
|
24
|
+
export class DelegationRangeRuleCollector {
|
|
25
|
+
type;
|
|
26
|
+
claim;
|
|
27
|
+
constructor(config) {
|
|
28
|
+
this.type = resolveRangeType("DelegationRangeRuleCollector", config?.type);
|
|
29
|
+
this.claim = resolveClaimName("DelegationRangeRuleCollector", config?.claim, DEFAULT_AUTHORIZATION_DETAILS_CLAIM);
|
|
30
|
+
}
|
|
31
|
+
async collect(context) {
|
|
32
|
+
if (entriesOfType(context.subject[this.claim], this.type) === null)
|
|
33
|
+
return [];
|
|
34
|
+
return [new WithinDelegationRange(requestedRangePath(context.resource.raw, context.action))];
|
|
35
|
+
}
|
|
36
|
+
}
|