@o3co/auth.policy-verifier.builtins 0.15.0 → 0.16.1
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
|
@@ -14,68 +14,30 @@ export type RequestContextAttributeCollectorConfig = AttributeMappingCollectorCo
|
|
|
14
14
|
/**
|
|
15
15
|
* Promotes declared fields of `CollectorContext.requestContext` into attributes.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* Those keys are what the engine decides from, and under the default server
|
|
42
|
-
* `scopes` / `userId` / `clientId` are read out of the signature-verified
|
|
43
|
-
* token; `requestContext` is the request body, which anyone holding a valid
|
|
44
|
-
* token fills in as they like. The two carry different trust and must not share
|
|
45
|
-
* a bucket.
|
|
46
|
-
*
|
|
47
|
-
* The reason it is refused outright rather than left to the operator is what
|
|
48
|
-
* `AttributePipeline` does with two collectors writing one key: array-valued
|
|
49
|
-
* entries **union**. So `{ from = "groups", to = "scopes" }` does not replace
|
|
50
|
-
* the token's scopes and lose an argument with `PayloadScopeCollector` — it
|
|
51
|
-
* quietly *adds to* them, and the request that arrives with
|
|
52
|
-
* `context.groups = ["admin:write"]` is authorized for a scope its token never
|
|
53
|
-
* carried. Nothing in the decision, the logs or the metrics distinguishes that
|
|
54
|
-
* from an issuer that granted it.
|
|
55
|
-
*
|
|
56
|
-
* A scalar key is not safe either, in two different ways. Two writers that
|
|
57
|
-
* disagree throw `AttributeConflictError`, which denies — fail-closed, but an
|
|
58
|
-
* unannounced outage rather than a refusal. And where the owning collector
|
|
59
|
-
* writes its key only *sometimes*, there is no second writer at all: cedar's
|
|
60
|
-
* `RequestFactsCollector` omits `requestResourceId` for an id-less resource
|
|
61
|
-
* such as `"document"`, so `{ from = "rid", to = "requestResourceId" }` would
|
|
62
|
-
* land unopposed and the Cedar resource entity would be built out of the
|
|
63
|
-
* caller's own request body.
|
|
64
|
-
*
|
|
65
|
-
* This is reachable only through operator configuration, which is why it is a
|
|
66
|
-
* configuration error rather than a vulnerability. It is refused at
|
|
67
|
-
* construction — boot, not first request — so a deployment that wrote it never
|
|
68
|
-
* serves a decision. Every other destination keeps working: the *field* may be
|
|
69
|
-
* called anything (`{ from = "scopes", to = "requestedScopes" }` is fine), only
|
|
70
|
-
* the attribute key is reserved.
|
|
71
|
-
*
|
|
72
|
-
* ```hocon
|
|
73
|
-
* { collector = "RequestContextAttributeCollector"
|
|
74
|
-
* attributes = [
|
|
75
|
-
* { from = "tenant.id", to = "tenantId" }
|
|
76
|
-
* { from = "groups", type = "string[]" }
|
|
77
|
-
* ] }
|
|
78
|
-
* ```
|
|
17
|
+
* Core assumes no shape for `requestContext` and keeps its `ATTR_*` vocabulary
|
|
18
|
+
* to OAuth/OIDC/RBAC standards, so this collector invents no vocabulary either:
|
|
19
|
+
* the operator declares which fields to read and what to call them, and nothing
|
|
20
|
+
* undeclared is promoted. That declaration is the trust boundary —
|
|
21
|
+
* `requestContext` is caller-supplied and unvalidated, so a field the config did
|
|
22
|
+
* not name cannot reach a rule, and one whose value does not match its declared
|
|
23
|
+
* type is dropped rather than passed along.
|
|
24
|
+
*
|
|
25
|
+
* A mapping's `to` may not name a key any package has reserved: core's five —
|
|
26
|
+
* `scopes`, `permissions`, `roles`, `userId`, `clientId` — and whatever other
|
|
27
|
+
* packages reserved before this collector is constructed (cedar's four
|
|
28
|
+
* `request*` keys, once it is imported). The check is a lookup in core's
|
|
29
|
+
* registry at construction; a later reservation is not seen. Those keys are
|
|
30
|
+
* what the engine decides from, written by the deployment, and the request
|
|
31
|
+
* body must not join them: `AttributePipeline` unions array-valued attributes,
|
|
32
|
+
* so `{ from = "groups", to = "scopes" }` would quietly add to the token's
|
|
33
|
+
* scopes; a scalar key written with a different value throws
|
|
34
|
+
* `AttributeConflictError` and denies, and where its owner writes it only
|
|
35
|
+
* sometimes (cedar's `requestResourceId` for an id-less resource) the
|
|
36
|
+
* caller's value stands unopposed. Such a mapping is a configuration error,
|
|
37
|
+
* refused at construction so the deployment fails at boot. Only the attribute
|
|
38
|
+
* key is reserved: `{ from = "scopes", to = "requestedScopes" }` is fine.
|
|
39
|
+
*
|
|
40
|
+
* Configuration, with an example: the package README, RequestContextAttributeCollector.
|
|
79
41
|
*/
|
|
80
42
|
export declare class RequestContextAttributeCollector implements AttributeCollector {
|
|
81
43
|
private readonly mappings;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestContextAttributeCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/RequestContextAttributeCollector.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACX,kBAAkB,EAElB,UAAU,EACV,gBAAgB,EAChB,MAAM,iCAAiC,CAAC;AAMzC,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,+BAA+B,EACpC,KAAK,oBAAoB,EAIzB,MAAM,yBAAyB,CAAC;AAEjC,yDAAyD;AACzD,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,8BAA8B,GAAG,gBAAgB,CAAC;AAE9D,mEAAmE;AACnE,MAAM,MAAM,sCAAsC,GAAG,+BAA+B,CAAC;AAErF
|
|
1
|
+
{"version":3,"file":"RequestContextAttributeCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/RequestContextAttributeCollector.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACX,kBAAkB,EAElB,UAAU,EACV,gBAAgB,EAChB,MAAM,iCAAiC,CAAC;AAMzC,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,+BAA+B,EACpC,KAAK,oBAAoB,EAIzB,MAAM,yBAAyB,CAAC;AAEjC,yDAAyD;AACzD,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,8BAA8B,GAAG,gBAAgB,CAAC;AAE9D,mEAAmE;AACnE,MAAM,MAAM,sCAAsC,GAAG,+BAA+B,CAAC;AAErF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,gCAAiC,YAAW,kBAAkB;IAC1E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;gBAE1C,MAAM,EAAE,sCAAsC;IAOpD,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;CAQ7D"}
|
|
@@ -5,75 +5,37 @@ import { parseAttributeMappings, promoteMappings, } from "./_attributeMapping.mj
|
|
|
5
5
|
/**
|
|
6
6
|
* Promotes declared fields of `CollectorContext.requestContext` into attributes.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Core assumes no shape for `requestContext` and keeps its `ATTR_*` vocabulary
|
|
9
|
+
* to OAuth/OIDC/RBAC standards, so this collector invents no vocabulary either:
|
|
10
|
+
* the operator declares which fields to read and what to call them, and nothing
|
|
11
|
+
* undeclared is promoted. That declaration is the trust boundary —
|
|
12
|
+
* `requestContext` is caller-supplied and unvalidated, so a field the config did
|
|
13
|
+
* not name cannot reach a rule, and one whose value does not match its declared
|
|
14
|
+
* type is dropped rather than passed along.
|
|
12
15
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* A mapping's `to` may not name a key any package has reserved: core's five —
|
|
17
|
+
* `scopes`, `permissions`, `roles`, `userId`, `clientId` — and whatever other
|
|
18
|
+
* packages reserved before this collector is constructed (cedar's four
|
|
19
|
+
* `request*` keys, once it is imported). The check is a lookup in core's
|
|
20
|
+
* registry at construction; a later reservation is not seen. Those keys are
|
|
21
|
+
* what the engine decides from, written by the deployment, and the request
|
|
22
|
+
* body must not join them: `AttributePipeline` unions array-valued attributes,
|
|
23
|
+
* so `{ from = "groups", to = "scopes" }` would quietly add to the token's
|
|
24
|
+
* scopes; a scalar key written with a different value throws
|
|
25
|
+
* `AttributeConflictError` and denies, and where its owner writes it only
|
|
26
|
+
* sometimes (cedar's `requestResourceId` for an id-less resource) the
|
|
27
|
+
* caller's value stands unopposed. Such a mapping is a configuration error,
|
|
28
|
+
* refused at construction so the deployment fails at boot. Only the attribute
|
|
29
|
+
* key is reserved: `{ from = "scopes", to = "requestedScopes" }` is fine.
|
|
20
30
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* A mapping's `to` may not name a key any package has reserved. Core reserves
|
|
24
|
-
* five — `scopes`, `permissions`, `roles`, `userId`, `clientId` — and every
|
|
25
|
-
* package that owns attribute vocabulary reserves its own at module load:
|
|
26
|
-
* `packages/cedar` reserves `requestAction`, `requestResourceType`,
|
|
27
|
-
* `requestResourceId` and `requestResourceRaw`. The check is a lookup in core's
|
|
28
|
-
* registry, not a list this collector or core keeps by hand, so a package core
|
|
29
|
-
* cannot see is covered by importing it — which a composition naming that
|
|
30
|
-
* package's collectors has already done.
|
|
31
|
-
*
|
|
32
|
-
* Those keys are what the engine decides from, and under the default server
|
|
33
|
-
* `scopes` / `userId` / `clientId` are read out of the signature-verified
|
|
34
|
-
* token; `requestContext` is the request body, which anyone holding a valid
|
|
35
|
-
* token fills in as they like. The two carry different trust and must not share
|
|
36
|
-
* a bucket.
|
|
37
|
-
*
|
|
38
|
-
* The reason it is refused outright rather than left to the operator is what
|
|
39
|
-
* `AttributePipeline` does with two collectors writing one key: array-valued
|
|
40
|
-
* entries **union**. So `{ from = "groups", to = "scopes" }` does not replace
|
|
41
|
-
* the token's scopes and lose an argument with `PayloadScopeCollector` — it
|
|
42
|
-
* quietly *adds to* them, and the request that arrives with
|
|
43
|
-
* `context.groups = ["admin:write"]` is authorized for a scope its token never
|
|
44
|
-
* carried. Nothing in the decision, the logs or the metrics distinguishes that
|
|
45
|
-
* from an issuer that granted it.
|
|
46
|
-
*
|
|
47
|
-
* A scalar key is not safe either, in two different ways. Two writers that
|
|
48
|
-
* disagree throw `AttributeConflictError`, which denies — fail-closed, but an
|
|
49
|
-
* unannounced outage rather than a refusal. And where the owning collector
|
|
50
|
-
* writes its key only *sometimes*, there is no second writer at all: cedar's
|
|
51
|
-
* `RequestFactsCollector` omits `requestResourceId` for an id-less resource
|
|
52
|
-
* such as `"document"`, so `{ from = "rid", to = "requestResourceId" }` would
|
|
53
|
-
* land unopposed and the Cedar resource entity would be built out of the
|
|
54
|
-
* caller's own request body.
|
|
55
|
-
*
|
|
56
|
-
* This is reachable only through operator configuration, which is why it is a
|
|
57
|
-
* configuration error rather than a vulnerability. It is refused at
|
|
58
|
-
* construction — boot, not first request — so a deployment that wrote it never
|
|
59
|
-
* serves a decision. Every other destination keeps working: the *field* may be
|
|
60
|
-
* called anything (`{ from = "scopes", to = "requestedScopes" }` is fine), only
|
|
61
|
-
* the attribute key is reserved.
|
|
62
|
-
*
|
|
63
|
-
* ```hocon
|
|
64
|
-
* { collector = "RequestContextAttributeCollector"
|
|
65
|
-
* attributes = [
|
|
66
|
-
* { from = "tenant.id", to = "tenantId" }
|
|
67
|
-
* { from = "groups", type = "string[]" }
|
|
68
|
-
* ] }
|
|
69
|
-
* ```
|
|
31
|
+
* Configuration, with an example: the package README, RequestContextAttributeCollector.
|
|
70
32
|
*/
|
|
71
33
|
export class RequestContextAttributeCollector {
|
|
72
34
|
mappings;
|
|
73
35
|
constructor(config) {
|
|
74
36
|
// Every reserved destination is refused here — the source is the caller's
|
|
75
37
|
// body. The declaration parsing is shared with the collector that reads
|
|
76
|
-
// verified claims
|
|
38
|
+
// verified claims; the policy is this collector's own.
|
|
77
39
|
this.mappings = parseAttributeMappings("RequestContextAttributeCollector", config, refusal);
|
|
78
40
|
}
|
|
79
41
|
async collect(context) {
|
|
@@ -87,17 +49,13 @@ export class RequestContextAttributeCollector {
|
|
|
87
49
|
}
|
|
88
50
|
}
|
|
89
51
|
/**
|
|
90
|
-
* Why this mapping is refused, in the words of whoever owns the key.
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* package's keys get its name and its own stated reason instead: asserting they
|
|
94
|
-
* are "the core vocabulary" would be false, and the operator's next move is to
|
|
95
|
-
* go read that package's docs, so the message says which one.
|
|
52
|
+
* Why this mapping is refused, in the words of whoever owns the key. Core's
|
|
53
|
+
* five get the trust boundary they sit on; another package's keys get its name
|
|
54
|
+
* and its own stated reason, so the operator knows whose docs to read.
|
|
96
55
|
*
|
|
97
|
-
* The suggested rename is drawn from core's registry rather than
|
|
98
|
-
* `request<Key
|
|
99
|
-
*
|
|
100
|
-
* guard.
|
|
56
|
+
* The suggested rename is drawn from core's registry rather than always
|
|
57
|
+
* `request<Key>`: `request*` is cedar's namespace, and advice landing there
|
|
58
|
+
* would be refused by this same guard.
|
|
101
59
|
*/
|
|
102
60
|
function refusal(index, reservation) {
|
|
103
61
|
const { key, owner, reason } = reservation;
|
|
@@ -111,8 +69,9 @@ function refusal(index, reservation) {
|
|
|
111
69
|
: `RequestContextAttributeCollector: attributes[${index}] maps onto the reserved attribute "${key}", ` +
|
|
112
70
|
`which belongs to ${owner}${reason === undefined ? "" : ` — ${reason}`}. ` +
|
|
113
71
|
"That package writes the key from what the deployment established, while requestContext is " +
|
|
114
|
-
"caller-supplied: where both write
|
|
115
|
-
"where
|
|
116
|
-
"
|
|
72
|
+
"caller-supplied: where both write a scalar key the values collide and every such request is " +
|
|
73
|
+
"denied, where both write a list key the caller's entries join the package's, and where that " +
|
|
74
|
+
"package writes nothing for a given request the caller's value stands unopposed as the one the " +
|
|
75
|
+
"deployment was supposed to supply. ";
|
|
117
76
|
return `${head}Promote the field under a key of your own (for example "${suggestUnreservedAttributeKey(key)}").`;
|
|
118
77
|
}
|
|
@@ -4,19 +4,17 @@ import type { AttributeCollector, Attributes, CollectorContext } from "@o3co/aut
|
|
|
4
4
|
* `ATTR_PERMISSIONS`, independent of the JWT payload. Useful for
|
|
5
5
|
* environments where permissions are static per deployment.
|
|
6
6
|
*
|
|
7
|
-
* The list is copied at construction
|
|
8
|
-
*
|
|
7
|
+
* The list is copied at construction: a caller that mutates the config or its
|
|
8
|
+
* array afterwards changes nothing this collector emits.
|
|
9
9
|
*
|
|
10
|
-
* Only an array is accepted
|
|
10
|
+
* Only an array is accepted. Anything else — a missing field, `null`, a
|
|
11
11
|
* number, an object, a string — is refused with a `TypeError` naming the
|
|
12
12
|
* collector and the field, so a misconfigured deployment fails at boot. A
|
|
13
|
-
* string
|
|
14
|
-
* `
|
|
15
|
-
* as grant-all.
|
|
13
|
+
* copied string would split `"posts.*"` into single characters, and
|
|
14
|
+
* `HasPermission` honours a lone `*` as grant-all.
|
|
16
15
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* an entry matches no requirement and grants nothing.
|
|
16
|
+
* Entries are not checked; a non-string one is copied as it is and is inert,
|
|
17
|
+
* since `HasPermission` skips every permission that is not a string.
|
|
20
18
|
*/
|
|
21
19
|
export declare class StaticPermissionCollector implements AttributeCollector {
|
|
22
20
|
private readonly permissions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StaticPermissionCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/StaticPermissionCollector.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACX,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,MAAM,iCAAiC,CAAC;AAGzC
|
|
1
|
+
{"version":3,"file":"StaticPermissionCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/StaticPermissionCollector.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACX,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,MAAM,iCAAiC,CAAC;AAGzC;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IACnE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;gBAEpC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,EAAE,CAAA;KAAE;IAUvC,OAAO,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;CAG9D"}
|
|
@@ -6,19 +6,17 @@ import { ATTR_PERMISSIONS } from "@o3co/auth.policy-verifier.core";
|
|
|
6
6
|
* `ATTR_PERMISSIONS`, independent of the JWT payload. Useful for
|
|
7
7
|
* environments where permissions are static per deployment.
|
|
8
8
|
*
|
|
9
|
-
* The list is copied at construction
|
|
10
|
-
*
|
|
9
|
+
* The list is copied at construction: a caller that mutates the config or its
|
|
10
|
+
* array afterwards changes nothing this collector emits.
|
|
11
11
|
*
|
|
12
|
-
* Only an array is accepted
|
|
12
|
+
* Only an array is accepted. Anything else — a missing field, `null`, a
|
|
13
13
|
* number, an object, a string — is refused with a `TypeError` naming the
|
|
14
14
|
* collector and the field, so a misconfigured deployment fails at boot. A
|
|
15
|
-
* string
|
|
16
|
-
* `
|
|
17
|
-
* as grant-all.
|
|
15
|
+
* copied string would split `"posts.*"` into single characters, and
|
|
16
|
+
* `HasPermission` honours a lone `*` as grant-all.
|
|
18
17
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* an entry matches no requirement and grants nothing.
|
|
18
|
+
* Entries are not checked; a non-string one is copied as it is and is inert,
|
|
19
|
+
* since `HasPermission` skips every permission that is not a string.
|
|
22
20
|
*/
|
|
23
21
|
export class StaticPermissionCollector {
|
|
24
22
|
permissions;
|
|
@@ -4,18 +4,17 @@ import type { AttributeCollector, Attributes, CollectorContext, Role } from "@o3
|
|
|
4
4
|
* `ATTR_ROLES`, independent of the JWT payload. Each role bundles a name and
|
|
5
5
|
* the permissions it implies.
|
|
6
6
|
*
|
|
7
|
-
* The list
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* The list, each role in it and each role's `permissions` array are copied at
|
|
8
|
+
* construction: a caller that mutates the config, its array or a `Role` in it
|
|
9
|
+
* afterwards changes nothing this collector emits.
|
|
10
10
|
*
|
|
11
|
-
* Only an array is accepted
|
|
12
|
-
* number, an object, a string
|
|
13
|
-
* collector and the field, so a
|
|
14
|
-
*
|
|
15
|
-
* it would be in `StaticPermissionCollector`.
|
|
11
|
+
* Only an array is accepted. Anything else — a missing field, `null`, a
|
|
12
|
+
* number, an object, a string (which a copy would split into characters) — is
|
|
13
|
+
* refused with a `TypeError` naming the collector and the field, so a
|
|
14
|
+
* misconfigured deployment fails at boot.
|
|
16
15
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Entries are not checked: a malformed `Role` is copied as it is (see
|
|
17
|
+
* `copyRole`) and is inert, since `HasPermission` ignores a role that is not an
|
|
19
18
|
* object or whose `permissions` is not an array, and a non-string permission
|
|
20
19
|
* inside a role. Each collect returns a shallow copy of the list, so its `Role`
|
|
21
20
|
* copies are shared between the outputs of different collects.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StaticRoleCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/StaticRoleCollector.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACX,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,IAAI,EACJ,MAAM,iCAAiC,CAAC;AAGzC
|
|
1
|
+
{"version":3,"file":"StaticRoleCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/StaticRoleCollector.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACX,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,IAAI,EACJ,MAAM,iCAAiC,CAAC;AAGzC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,mBAAoB,YAAW,kBAAkB;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;gBAE5B,MAAM,EAAE;QAAE,KAAK,EAAE,IAAI,EAAE,CAAA;KAAE;IAU/B,OAAO,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;CAG9D"}
|
|
@@ -6,18 +6,17 @@ import { ATTR_ROLES } from "@o3co/auth.policy-verifier.core";
|
|
|
6
6
|
* `ATTR_ROLES`, independent of the JWT payload. Each role bundles a name and
|
|
7
7
|
* the permissions it implies.
|
|
8
8
|
*
|
|
9
|
-
* The list
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* The list, each role in it and each role's `permissions` array are copied at
|
|
10
|
+
* construction: a caller that mutates the config, its array or a `Role` in it
|
|
11
|
+
* afterwards changes nothing this collector emits.
|
|
12
12
|
*
|
|
13
|
-
* Only an array is accepted
|
|
14
|
-
* number, an object, a string
|
|
15
|
-
* collector and the field, so a
|
|
16
|
-
*
|
|
17
|
-
* it would be in `StaticPermissionCollector`.
|
|
13
|
+
* Only an array is accepted. Anything else — a missing field, `null`, a
|
|
14
|
+
* number, an object, a string (which a copy would split into characters) — is
|
|
15
|
+
* refused with a `TypeError` naming the collector and the field, so a
|
|
16
|
+
* misconfigured deployment fails at boot.
|
|
18
17
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* Entries are not checked: a malformed `Role` is copied as it is (see
|
|
19
|
+
* `copyRole`) and is inert, since `HasPermission` ignores a role that is not an
|
|
21
20
|
* object or whose `permissions` is not an array, and a non-string permission
|
|
22
21
|
* inside a role. Each collect returns a shallow copy of the list, so its `Role`
|
|
23
22
|
* copies are shared between the outputs of different collects.
|
|
@@ -40,7 +39,7 @@ export class StaticRoleCollector {
|
|
|
40
39
|
*
|
|
41
40
|
* Nothing about the entry is validated here, and a malformed one keeps its
|
|
42
41
|
* shape rather than being coerced — `HasPermission` ignores a role that is not
|
|
43
|
-
* an object or whose `permissions` is not an array
|
|
42
|
+
* an object or whose `permissions` is not an array, and spreading a bare
|
|
44
43
|
* string under `permissions` would splay it into characters it then honours.
|
|
45
44
|
*/
|
|
46
45
|
function copyRole(role) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AttributeKeyReservation, Attributes } from "@o3co/auth.policy-verifier.core";
|
|
2
|
+
import "../keys.mjs";
|
|
2
3
|
/** Types a field may be promoted as. */
|
|
3
4
|
export type AttributeMappingType = "string" | "number" | "boolean" | "string[]";
|
|
4
5
|
export declare const ATTRIBUTE_MAPPING_TYPES: readonly AttributeMappingType[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_attributeMapping.d.mts","sourceRoot":"","sources":["../../src/collectors/_attributeMapping.mts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"_attributeMapping.d.mts","sourceRoot":"","sources":["../../src/collectors/_attributeMapping.mts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAI3F,OAAO,aAAa,CAAC;AAErB,wCAAwC;AACxC,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;AAEhF,eAAO,MAAM,uBAAuB,EAAE,SAAS,oBAAoB,EAKlE,CAAC;AAEF,4BAA4B;AAC5B,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,yFAAyF;IACzF,IAAI,CAAC,EAAE,oBAAoB,CAAC;CAC5B;AAED,2DAA2D;AAC3D,MAAM,WAAW,+BAA+B;IAC/C,wDAAwD;IACxD,UAAU,EAAE,gBAAgB,EAAE,CAAC;CAC/B;AAED,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AAElE;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC/B,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,uBAAuB,KAChC,MAAM,GAAG,SAAS,CAAC;AAExB;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,+BAA+B,GAAG,SAAS,EACnD,iBAAiB,EAAE,iBAAiB,GAClC,wBAAwB,EAAE,CA4B5B;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAS7E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAW/E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,QAAQ,EAAE,SAAS,wBAAwB,EAAE,EAC7C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,UAAU,CASZ"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { attributeKeyReservation } from "@o3co/auth.policy-verifier.core";
|
|
4
|
+
// This package's own keys are reserved by loading their module, which a
|
|
5
|
+
// mapping collector must not depend on the package entry to have done.
|
|
6
|
+
import "../keys.mjs";
|
|
4
7
|
export const ATTRIBUTE_MAPPING_TYPES = [
|
|
5
8
|
"string",
|
|
6
9
|
"number",
|
|
@@ -12,9 +12,8 @@ export declare function resolveClaimName(collector: string, raw: unknown, fallba
|
|
|
12
12
|
* Reads a scope claim as a list. The OAuth `scope` claim is a space-delimited
|
|
13
13
|
* string; Okta's `scp` and Auth0's `permissions` are arrays of strings. Either
|
|
14
14
|
* shape yields the non-empty entries. Anything else — absent, a number, a list
|
|
15
|
-
* carrying a non-string — asserts no capability at all
|
|
16
|
-
*
|
|
17
|
-
* that is not a scope list.
|
|
15
|
+
* carrying a non-string — asserts no capability at all: fail closed rather
|
|
16
|
+
* than pick the strings out of a value that is not a scope list.
|
|
18
17
|
*/
|
|
19
18
|
export declare function scopesFrom(value: unknown): string[];
|
|
20
19
|
//# sourceMappingURL=_claims.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_claims.d.mts","sourceRoot":"","sources":["../../src/collectors/_claims.mts"],"names":[],"mappings":"AAaA,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM1F;AAED
|
|
1
|
+
{"version":3,"file":"_claims.d.mts","sourceRoot":"","sources":["../../src/collectors/_claims.mts"],"names":[],"mappings":"AAaA,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM1F;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAMnD"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
/*
|
|
4
|
-
* Which claim carries the scopes, and how its value is read
|
|
4
|
+
* Which claim carries the scopes, and how its value is read.
|
|
5
5
|
* `PayloadScopeCollector` and `ResourceActionScopeRuleCollector` share the
|
|
6
6
|
* default claim name and refuse the same bad `claim` option; only
|
|
7
7
|
* `PayloadScopeCollector` reads the value (`scopesFrom`), while the rule
|
|
@@ -30,9 +30,8 @@ export function resolveClaimName(collector, raw, fallback) {
|
|
|
30
30
|
* Reads a scope claim as a list. The OAuth `scope` claim is a space-delimited
|
|
31
31
|
* string; Okta's `scp` and Auth0's `permissions` are arrays of strings. Either
|
|
32
32
|
* shape yields the non-empty entries. Anything else — absent, a number, a list
|
|
33
|
-
* carrying a non-string — asserts no capability at all
|
|
34
|
-
*
|
|
35
|
-
* that is not a scope list.
|
|
33
|
+
* carrying a non-string — asserts no capability at all: fail closed rather
|
|
34
|
+
* than pick the strings out of a value that is not a scope list.
|
|
36
35
|
*/
|
|
37
36
|
export function scopesFrom(value) {
|
|
38
37
|
if (typeof value === "string")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** The claim RFC 9396 carries the entries in, and the default. */
|
|
2
|
+
export declare const DEFAULT_AUTHORIZATION_DETAILS_CLAIM = "authorization_details";
|
|
3
|
+
/** One element of a path: a type, and an id when the path names one. */
|
|
4
|
+
export interface RangeElement {
|
|
5
|
+
type: string;
|
|
6
|
+
id?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Reads `path` as `(type(:id)?.)*action`, or `null` when it is not one:
|
|
10
|
+
*
|
|
11
|
+
* - `type` and `action` are `[a-z][a-z0-9_]*`;
|
|
12
|
+
* - `id` is `[A-Za-z0-9_~-]`, any other character percent-encoded with
|
|
13
|
+
* upper-case hex. An id is compared as written, so `%41` and `A` are two
|
|
14
|
+
* ids: the issuer and the resource must encode alike;
|
|
15
|
+
* - the action, the last element, carries no id.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseRangePath(path: string): RangeElement[] | null;
|
|
18
|
+
/**
|
|
19
|
+
* The requested path — the raw resource, then the action — or `null` when the
|
|
20
|
+
* action is not one action of the grammar. The action is checked on its own
|
|
21
|
+
* before the join: an action of several elements (`report.delete`) would
|
|
22
|
+
* re-split the joined path, so a request on the parent `project:p1` would
|
|
23
|
+
* read as one on `project:p1.report`, which a range may contain.
|
|
24
|
+
*/
|
|
25
|
+
export declare function requestedRangePath(resource: string, action: string): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Whether the range entry `entry` contains the requested `path`: a
|
|
28
|
+
* segment-wise prefix of it, each element of the same type, and an element
|
|
29
|
+
* that names no id containing the same type with any id. The entry's last
|
|
30
|
+
* element is compared as a type whether it names an action or not, so
|
|
31
|
+
* `a:1.run` contains `a:1.run.more`. Either side outside the grammar contains
|
|
32
|
+
* nothing.
|
|
33
|
+
*/
|
|
34
|
+
export declare function rangeContains(entry: string, path: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The claim's entries of `type`, or `null` when the token has no range: the
|
|
37
|
+
* claim is absent, or a list of RFC 9396 entries none of which is of the type.
|
|
38
|
+
* A token cannot shed its range by spelling it badly: an entry of the type
|
|
39
|
+
* counts however malformed its path, and a claim present in another shape — not
|
|
40
|
+
* a list, or a list holding something that is not an entry object — is a range
|
|
41
|
+
* with no entries, which contains nothing.
|
|
42
|
+
*/
|
|
43
|
+
export declare function entriesOfType(claim: unknown, type: string): object[] | null;
|
|
44
|
+
/** The entries' paths that are in the grammar; the rest are left out, which narrows the range. */
|
|
45
|
+
export declare function rangePaths(entries: object[]): string[];
|
|
46
|
+
/** Resolves a collector's `type` option, which has no default: it is the deployment's delegation type. */
|
|
47
|
+
export declare function resolveRangeType(collector: string, raw: unknown): string;
|
|
48
|
+
//# sourceMappingURL=range.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"range.d.mts","sourceRoot":"","sources":["../../src/delegation/range.mts"],"names":[],"mappings":"AAeA,kEAAkE;AAClE,eAAO,MAAM,mCAAmC,0BAA0B,CAAC;AAE3E,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAOD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,IAAI,CAiBlE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAElF;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CASlE;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAK3E;AAMD,kGAAkG;AAClG,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAItD;AAED,0GAA0G;AAC1G,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAOxE"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/*
|
|
4
|
+
* A delegated token's range, as the provider-verifier claims contract states
|
|
5
|
+
* it (o3co/auth, docs/claims-contract.md): RFC 9396 `authorization_details`
|
|
6
|
+
* entries of one type, each carrying a path in the grammar
|
|
7
|
+
* `(type(:id)?.)*action`, and the rule by which an entry contains a requested
|
|
8
|
+
* path. Shared by `DelegationRangeCollector`, which reads the range out of
|
|
9
|
+
* the token, `DelegationRangeRuleCollector`, which decides whether a token
|
|
10
|
+
* has one, and `WithinDelegationRange`, which decides containment. Nothing is
|
|
11
|
+
* normalized: a path is compared as written, as `HasScope` compares scopes.
|
|
12
|
+
* The attribute key the range travels under is `../keys.mts`'s.
|
|
13
|
+
*/
|
|
14
|
+
/** The claim RFC 9396 carries the entries in, and the default. */
|
|
15
|
+
export const DEFAULT_AUTHORIZATION_DETAILS_CLAIM = "authorization_details";
|
|
16
|
+
/** A type, or the action that ends a path. */
|
|
17
|
+
const NAME = /^[a-z][a-z0-9_]*$/;
|
|
18
|
+
/** An id: unreserved characters, anything else percent-encoded in upper-case hex. */
|
|
19
|
+
const ID = /^(?:[A-Za-z0-9_~-]|%[0-9A-F]{2})+$/;
|
|
20
|
+
/**
|
|
21
|
+
* Reads `path` as `(type(:id)?.)*action`, or `null` when it is not one:
|
|
22
|
+
*
|
|
23
|
+
* - `type` and `action` are `[a-z][a-z0-9_]*`;
|
|
24
|
+
* - `id` is `[A-Za-z0-9_~-]`, any other character percent-encoded with
|
|
25
|
+
* upper-case hex. An id is compared as written, so `%41` and `A` are two
|
|
26
|
+
* ids: the issuer and the resource must encode alike;
|
|
27
|
+
* - the action, the last element, carries no id.
|
|
28
|
+
*/
|
|
29
|
+
export function parseRangePath(path) {
|
|
30
|
+
if (typeof path !== "string" || path === "")
|
|
31
|
+
return null;
|
|
32
|
+
const parts = path.split(".");
|
|
33
|
+
const elements = [];
|
|
34
|
+
for (const [index, part] of parts.entries()) {
|
|
35
|
+
const pieces = part.split(":");
|
|
36
|
+
if (pieces.length > 2)
|
|
37
|
+
return null;
|
|
38
|
+
const [type, id] = pieces;
|
|
39
|
+
if (!NAME.test(type))
|
|
40
|
+
return null;
|
|
41
|
+
if (id === undefined) {
|
|
42
|
+
elements.push({ type });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (index === parts.length - 1 || !ID.test(id))
|
|
46
|
+
return null;
|
|
47
|
+
elements.push({ type, id });
|
|
48
|
+
}
|
|
49
|
+
return elements;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The requested path — the raw resource, then the action — or `null` when the
|
|
53
|
+
* action is not one action of the grammar. The action is checked on its own
|
|
54
|
+
* before the join: an action of several elements (`report.delete`) would
|
|
55
|
+
* re-split the joined path, so a request on the parent `project:p1` would
|
|
56
|
+
* read as one on `project:p1.report`, which a range may contain.
|
|
57
|
+
*/
|
|
58
|
+
export function requestedRangePath(resource, action) {
|
|
59
|
+
return NAME.test(action) ? `${resource}.${action}` : null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Whether the range entry `entry` contains the requested `path`: a
|
|
63
|
+
* segment-wise prefix of it, each element of the same type, and an element
|
|
64
|
+
* that names no id containing the same type with any id. The entry's last
|
|
65
|
+
* element is compared as a type whether it names an action or not, so
|
|
66
|
+
* `a:1.run` contains `a:1.run.more`. Either side outside the grammar contains
|
|
67
|
+
* nothing.
|
|
68
|
+
*/
|
|
69
|
+
export function rangeContains(entry, path) {
|
|
70
|
+
const within = parseRangePath(entry);
|
|
71
|
+
const requested = parseRangePath(path);
|
|
72
|
+
if (within === null || requested === null || within.length > requested.length)
|
|
73
|
+
return false;
|
|
74
|
+
return within.every((element, index) => element.type === requested[index].type &&
|
|
75
|
+
(element.id === undefined || element.id === requested[index].id));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The claim's entries of `type`, or `null` when the token has no range: the
|
|
79
|
+
* claim is absent, or a list of RFC 9396 entries none of which is of the type.
|
|
80
|
+
* A token cannot shed its range by spelling it badly: an entry of the type
|
|
81
|
+
* counts however malformed its path, and a claim present in another shape — not
|
|
82
|
+
* a list, or a list holding something that is not an entry object — is a range
|
|
83
|
+
* with no entries, which contains nothing.
|
|
84
|
+
*/
|
|
85
|
+
export function entriesOfType(claim, type) {
|
|
86
|
+
if (claim === undefined)
|
|
87
|
+
return null;
|
|
88
|
+
if (!Array.isArray(claim) || !claim.every(isEntryObject))
|
|
89
|
+
return [];
|
|
90
|
+
const entries = claim.filter((entry) => entry.type === type);
|
|
91
|
+
return entries.length > 0 ? entries : null;
|
|
92
|
+
}
|
|
93
|
+
function isEntryObject(entry) {
|
|
94
|
+
return typeof entry === "object" && entry !== null && !Array.isArray(entry);
|
|
95
|
+
}
|
|
96
|
+
/** The entries' paths that are in the grammar; the rest are left out, which narrows the range. */
|
|
97
|
+
export function rangePaths(entries) {
|
|
98
|
+
return entries
|
|
99
|
+
.map((entry) => entry.path)
|
|
100
|
+
.filter((path) => typeof path === "string" && parseRangePath(path) !== null);
|
|
101
|
+
}
|
|
102
|
+
/** Resolves a collector's `type` option, which has no default: it is the deployment's delegation type. */
|
|
103
|
+
export function resolveRangeType(collector, raw) {
|
|
104
|
+
if (typeof raw !== "string" || raw === "") {
|
|
105
|
+
throw new Error(`${collector}: type must be the authorization_details type the delegation range is carried under, a non-empty string`);
|
|
106
|
+
}
|
|
107
|
+
return raw;
|
|
108
|
+
}
|