@stigmer/server 3.27.1 → 3.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/authorization/authorizer.d.ts +7 -5
- package/dist/authorization/authorizer.d.ts.map +1 -1
- package/dist/authorization/authorizer.js +7 -5
- package/dist/authorization/authorizer.js.map +1 -1
- package/dist/authorization/derived-tuples.d.ts +3 -1
- package/dist/authorization/derived-tuples.d.ts.map +1 -1
- package/dist/authorization/derived-tuples.js +52 -2
- package/dist/authorization/derived-tuples.js.map +1 -1
- package/dist/authorization/evaluator.d.ts +9 -10
- package/dist/authorization/evaluator.d.ts.map +1 -1
- package/dist/authorization/evaluator.js.map +1 -1
- package/dist/authorization/list-read-scope.d.ts.map +1 -1
- package/dist/authorization/list-read-scope.js +29 -18
- package/dist/authorization/list-read-scope.js.map +1 -1
- package/dist/authorization/model/bindings.d.ts +41 -0
- package/dist/authorization/model/bindings.d.ts.map +1 -0
- package/dist/authorization/model/bindings.js +89 -0
- package/dist/authorization/model/bindings.js.map +1 -0
- package/dist/authorization/model/data/authorization-model.json +3114 -0
- package/dist/authorization/model/default-of.d.ts +2 -2
- package/dist/authorization/model/execution-viewer.d.ts +3 -0
- package/dist/authorization/model/execution-viewer.d.ts.map +1 -0
- package/dist/authorization/model/execution-viewer.js +50 -0
- package/dist/authorization/model/execution-viewer.js.map +1 -0
- package/dist/authorization/model/index.d.ts +33 -21
- package/dist/authorization/model/index.d.ts.map +1 -1
- package/dist/authorization/model/index.js +74 -52
- package/dist/authorization/model/index.js.map +1 -1
- package/dist/authorization/model/openfga-json.d.ts +18 -0
- package/dist/authorization/model/openfga-json.d.ts.map +1 -0
- package/dist/authorization/model/openfga-json.js +266 -0
- package/dist/authorization/model/openfga-json.js.map +1 -0
- package/dist/authorization/model/rewrite.d.ts +39 -67
- package/dist/authorization/model/rewrite.d.ts.map +1 -1
- package/dist/authorization/model/rewrite.js +1 -117
- package/dist/authorization/model/rewrite.js.map +1 -1
- package/dist/authorization/strict-json.d.ts +20 -0
- package/dist/authorization/strict-json.d.ts.map +1 -0
- package/dist/authorization/strict-json.js +45 -0
- package/dist/authorization/strict-json.js.map +1 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +9 -7
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/list-indexes.d.ts.map +1 -1
- package/dist/boot/list-indexes.js +6 -0
- package/dist/boot/list-indexes.js.map +1 -1
- package/dist/domain/iampolicy/list-index.d.ts +2 -0
- package/dist/domain/iampolicy/list-index.d.ts.map +1 -0
- package/dist/domain/iampolicy/list-index.js +32 -0
- package/dist/domain/iampolicy/list-index.js.map +1 -0
- package/dist/domain/iampolicy/resource-store.d.ts.map +1 -1
- package/dist/domain/iampolicy/resource-store.js +24 -11
- package/dist/domain/iampolicy/resource-store.js.map +1 -1
- package/dist/domain/workflowexecution/list-index.d.ts +1 -1
- package/dist/extensions/list-read-scope.d.ts +20 -3
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +22 -2
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/authorization/README.md +48 -39
- package/src/authorization/__tests__/authorizer.test.ts +5 -14
- package/src/authorization/__tests__/derived-tuples.test.ts +192 -17
- package/src/authorization/__tests__/evaluator.test.ts +53 -160
- package/src/authorization/__tests__/facts.test.ts +5 -11
- package/src/authorization/__tests__/list-read-scope.measure.test.ts +424 -62
- package/src/authorization/__tests__/list-read-scope.test.ts +15 -14
- package/src/authorization/__tests__/organization-directory.test.ts +2 -2
- package/src/authorization/__tests__/store-test-kit.ts +246 -0
- package/src/authorization/__tests__/store-tests.test.ts +91 -223
- package/src/authorization/__tests__/support.ts +30 -2
- package/src/authorization/__tests__/throwaway-model.ts +75 -0
- package/src/authorization/__tests__/wire-permissions.test.ts +141 -107
- package/src/authorization/authorizer.ts +7 -5
- package/src/authorization/derived-tuples.ts +105 -16
- package/src/authorization/evaluator.ts +8 -10
- package/src/authorization/list-read-scope.ts +37 -22
- package/src/authorization/model/__tests__/openfga-json.test.ts +353 -0
- package/src/authorization/model/__tests__/registry.test.ts +102 -276
- package/src/authorization/model/bindings.ts +131 -0
- package/src/authorization/model/data/authorization-model.json +3114 -0
- package/src/authorization/model/default-of.ts +2 -2
- package/src/authorization/model/execution-viewer.ts +56 -0
- package/src/authorization/model/index.ts +81 -72
- package/src/authorization/model/openfga-json.ts +310 -0
- package/src/authorization/model/rewrite.ts +39 -188
- package/src/authorization/strict-json.ts +52 -0
- package/src/boot/__tests__/list-indexes.test.ts +4 -0
- package/src/boot/compose.ts +9 -7
- package/src/boot/list-indexes.ts +6 -0
- package/src/domain/iampolicy/__tests__/resource-store.test.ts +65 -5
- package/src/domain/iampolicy/list-index.ts +33 -0
- package/src/domain/iampolicy/resource-store.ts +24 -11
- package/src/extensions/__tests__/built-in-authorization-composed.test.ts +3 -6
- package/src/extensions/list-read-scope.ts +32 -4
- package/src/index.ts +20 -44
- package/dist/authorization/model/agent.d.ts +0 -2
- package/dist/authorization/model/agent.d.ts.map +0 -1
- package/dist/authorization/model/agent.js +0 -43
- package/dist/authorization/model/agent.js.map +0 -1
- package/dist/authorization/model/agent_channel.d.ts +0 -2
- package/dist/authorization/model/agent_channel.d.ts.map +0 -1
- package/dist/authorization/model/agent_channel.js +0 -39
- package/dist/authorization/model/agent_channel.js.map +0 -1
- package/dist/authorization/model/agent_execution.d.ts +0 -2
- package/dist/authorization/model/agent_execution.d.ts.map +0 -1
- package/dist/authorization/model/agent_execution.js +0 -25
- package/dist/authorization/model/agent_execution.js.map +0 -1
- package/dist/authorization/model/agent_instance.d.ts +0 -2
- package/dist/authorization/model/agent_instance.d.ts.map +0 -1
- package/dist/authorization/model/agent_instance.js +0 -48
- package/dist/authorization/model/agent_instance.js.map +0 -1
- package/dist/authorization/model/agent_share.d.ts +0 -2
- package/dist/authorization/model/agent_share.d.ts.map +0 -1
- package/dist/authorization/model/agent_share.js +0 -29
- package/dist/authorization/model/agent_share.js.map +0 -1
- package/dist/authorization/model/api_key.d.ts +0 -2
- package/dist/authorization/model/api_key.d.ts.map +0 -1
- package/dist/authorization/model/api_key.js +0 -27
- package/dist/authorization/model/api_key.js.map +0 -1
- package/dist/authorization/model/artifact.d.ts +0 -2
- package/dist/authorization/model/artifact.d.ts.map +0 -1
- package/dist/authorization/model/artifact.js +0 -27
- package/dist/authorization/model/artifact.js.map +0 -1
- package/dist/authorization/model/channel_app.d.ts +0 -2
- package/dist/authorization/model/channel_app.d.ts.map +0 -1
- package/dist/authorization/model/channel_app.js +0 -28
- package/dist/authorization/model/channel_app.js.map +0 -1
- package/dist/authorization/model/environment.d.ts +0 -2
- package/dist/authorization/model/environment.d.ts.map +0 -1
- package/dist/authorization/model/environment.js +0 -33
- package/dist/authorization/model/environment.js.map +0 -1
- package/dist/authorization/model/execution_context.d.ts +0 -2
- package/dist/authorization/model/execution_context.d.ts.map +0 -1
- package/dist/authorization/model/execution_context.js +0 -19
- package/dist/authorization/model/execution_context.js.map +0 -1
- package/dist/authorization/model/iam_policy.d.ts +0 -2
- package/dist/authorization/model/iam_policy.d.ts.map +0 -1
- package/dist/authorization/model/iam_policy.js +0 -31
- package/dist/authorization/model/iam_policy.js.map +0 -1
- package/dist/authorization/model/identity_account.d.ts +0 -2
- package/dist/authorization/model/identity_account.d.ts.map +0 -1
- package/dist/authorization/model/identity_account.js +0 -25
- package/dist/authorization/model/identity_account.js.map +0 -1
- package/dist/authorization/model/mcp_server.d.ts +0 -2
- package/dist/authorization/model/mcp_server.d.ts.map +0 -1
- package/dist/authorization/model/mcp_server.js +0 -38
- package/dist/authorization/model/mcp_server.js.map +0 -1
- package/dist/authorization/model/memory.d.ts +0 -2
- package/dist/authorization/model/memory.d.ts.map +0 -1
- package/dist/authorization/model/memory.js +0 -26
- package/dist/authorization/model/memory.js.map +0 -1
- package/dist/authorization/model/oauth_app.d.ts +0 -2
- package/dist/authorization/model/oauth_app.d.ts.map +0 -1
- package/dist/authorization/model/oauth_app.js +0 -31
- package/dist/authorization/model/oauth_app.js.map +0 -1
- package/dist/authorization/model/organization.d.ts +0 -2
- package/dist/authorization/model/organization.d.ts.map +0 -1
- package/dist/authorization/model/organization.js +0 -58
- package/dist/authorization/model/organization.js.map +0 -1
- package/dist/authorization/model/platform_client.d.ts +0 -2
- package/dist/authorization/model/platform_client.d.ts.map +0 -1
- package/dist/authorization/model/platform_client.js +0 -31
- package/dist/authorization/model/platform_client.js.map +0 -1
- package/dist/authorization/model/plugin.d.ts +0 -2
- package/dist/authorization/model/plugin.d.ts.map +0 -1
- package/dist/authorization/model/plugin.js +0 -45
- package/dist/authorization/model/plugin.js.map +0 -1
- package/dist/authorization/model/schedule.d.ts +0 -2
- package/dist/authorization/model/schedule.d.ts.map +0 -1
- package/dist/authorization/model/schedule.js +0 -34
- package/dist/authorization/model/schedule.js.map +0 -1
- package/dist/authorization/model/session.d.ts +0 -2
- package/dist/authorization/model/session.d.ts.map +0 -1
- package/dist/authorization/model/session.js +0 -38
- package/dist/authorization/model/session.js.map +0 -1
- package/dist/authorization/model/skill.d.ts +0 -2
- package/dist/authorization/model/skill.d.ts.map +0 -1
- package/dist/authorization/model/skill.js +0 -39
- package/dist/authorization/model/skill.js.map +0 -1
- package/dist/authorization/model/workflow.d.ts +0 -2
- package/dist/authorization/model/workflow.d.ts.map +0 -1
- package/dist/authorization/model/workflow.js +0 -39
- package/dist/authorization/model/workflow.js.map +0 -1
- package/dist/authorization/model/workflow_execution.d.ts +0 -2
- package/dist/authorization/model/workflow_execution.d.ts.map +0 -1
- package/dist/authorization/model/workflow_execution.js +0 -33
- package/dist/authorization/model/workflow_execution.js.map +0 -1
- package/dist/authorization/model/workflow_instance.d.ts +0 -2
- package/dist/authorization/model/workflow_instance.d.ts.map +0 -1
- package/dist/authorization/model/workflow_instance.js +0 -96
- package/dist/authorization/model/workflow_instance.js.map +0 -1
- package/dist/authorization/store-test-kit.d.ts +0 -62
- package/dist/authorization/store-test-kit.d.ts.map +0 -1
- package/dist/authorization/store-test-kit.js +0 -265
- package/dist/authorization/store-test-kit.js.map +0 -1
- package/src/authorization/__tests__/fixtures/fga/agent-channel-owner.fga.yaml +0 -112
- package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +0 -177
- package/src/authorization/__tests__/fixtures/fga/agent-share-owner.fga.yaml +0 -109
- package/src/authorization/__tests__/fixtures/fga/artifact-org-and-owner.fga.yaml +0 -128
- package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +0 -318
- package/src/authorization/__tests__/fixtures/fga/channel-app-owner.fga.yaml +0 -125
- package/src/authorization/__tests__/fixtures/fga/channel-participation.fga.yaml +0 -201
- package/src/authorization/__tests__/fixtures/fga/channel-session-visibility.fga.yaml +0 -187
- package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +0 -214
- package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +0 -129
- package/src/authorization/__tests__/fixtures/fga/memory-subject-only.fga.yaml +0 -72
- package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +0 -308
- package/src/authorization/__tests__/fixtures/fga/org-shared-environment.fga.yaml +0 -144
- package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +0 -199
- package/src/authorization/__tests__/fixtures/fga/provider-standing-viewer.fga.yaml +0 -40
- package/src/authorization/__tests__/fixtures/fga/reserved-label-writer.fga.yaml +0 -41
- package/src/authorization/__tests__/fixtures/fga/schedule-owner.fga.yaml +0 -113
- package/src/authorization/__tests__/fixtures/fga/schedule-session-visibility.fga.yaml +0 -214
- package/src/authorization/__tests__/fixtures/fga/session-personal-resource.fga.yaml +0 -117
- package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +0 -190
- package/src/authorization/model/agent.ts +0 -64
- package/src/authorization/model/agent_channel.ts +0 -57
- package/src/authorization/model/agent_execution.ts +0 -33
- package/src/authorization/model/agent_instance.ts +0 -66
- package/src/authorization/model/agent_share.ts +0 -40
- package/src/authorization/model/api_key.ts +0 -28
- package/src/authorization/model/artifact.ts +0 -38
- package/src/authorization/model/channel_app.ts +0 -39
- package/src/authorization/model/environment.ts +0 -48
- package/src/authorization/model/execution_context.ts +0 -20
- package/src/authorization/model/iam_policy.ts +0 -42
- package/src/authorization/model/identity_account.ts +0 -26
- package/src/authorization/model/mcp_server.ts +0 -59
- package/src/authorization/model/memory.ts +0 -27
- package/src/authorization/model/oauth_app.ts +0 -43
- package/src/authorization/model/organization.ts +0 -59
- package/src/authorization/model/platform_client.ts +0 -43
- package/src/authorization/model/plugin.ts +0 -66
- package/src/authorization/model/schedule.ts +0 -49
- package/src/authorization/model/session.ts +0 -50
- package/src/authorization/model/skill.ts +0 -60
- package/src/authorization/model/workflow.ts +0 -60
- package/src/authorization/model/workflow_execution.ts +0 -46
- package/src/authorization/model/workflow_instance.ts +0 -125
- package/src/authorization/store-test-kit.ts +0 -395
|
@@ -1,61 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
* and
|
|
2
|
+
* The evaluator's vocabulary for the authorization model: the five
|
|
3
|
+
* relation-rewrite forms OpenFGA's JSON uses in the compiled model
|
|
4
|
+
* (data/authorization-model.json, read by openfga-json.ts), and a kind's
|
|
5
|
+
* declaration as the evaluator consumes it. The model itself is the
|
|
6
|
+
* `.fga` files under fga/model; this module only names its shapes.
|
|
5
7
|
*
|
|
6
|
-
* define organization: [organization]
|
|
8
|
+
* define organization: [organization] → this, subjects [organization]
|
|
7
9
|
* define owner: [identity_account] or admin from organization
|
|
8
|
-
* → union(
|
|
9
|
-
* define viewer: [identity_account, organization#
|
|
10
|
-
*
|
|
11
|
-
* → union(direct(objectOf(...), usersetOf(...), usersetOf(...)),
|
|
12
|
-
* computed("owner"), computed("platform_viewer"))
|
|
10
|
+
* → union(this, from("admin", "organization"))
|
|
11
|
+
* define viewer: [identity_account, organization#viewer] or owner
|
|
12
|
+
* → union(this, computed("owner"))
|
|
13
13
|
* define member: [identity_account] and viewer from organization
|
|
14
|
-
* → intersection(
|
|
15
|
-
* define can_view: viewer
|
|
14
|
+
* → intersection(this, from("viewer", "organization"))
|
|
15
|
+
* define can_view: viewer → computed("viewer")
|
|
16
16
|
*
|
|
17
17
|
* `and` exists for one shape: a relation that is true only while another
|
|
18
|
-
* holds too
|
|
18
|
+
* holds too (a team's member is a person granted the role AND still one
|
|
19
19
|
* of the organization's viewers, so leaving the organization ends every
|
|
20
|
-
* team-derived grant with no cleanup.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* refuses
|
|
24
|
-
* a line needs it, the transcript cannot be written and the gap is a
|
|
20
|
+
* team-derived grant with no cleanup). Operators nest to any depth, as
|
|
21
|
+
* the DSL's parentheses allow; the evaluator recurses through them.
|
|
22
|
+
* `but not`, wildcards (`type:*`) and conditions have no form here: the
|
|
23
|
+
* reader refuses them by name, and the day the model needs one is a
|
|
25
24
|
* design act.
|
|
26
25
|
*
|
|
27
|
-
* A subject type is an object type or a userset; the model admits no
|
|
28
|
-
* wildcard (`type:*`) and declares no condition, so neither has a form
|
|
29
|
-
* here. The day a line needs one, the transcript cannot be written and
|
|
30
|
-
* the gap is a design act.
|
|
31
|
-
*
|
|
32
|
-
* A declaration file (model/<kind>.ts) is a TRANSCRIPT of its `.fga`
|
|
33
|
-
* file: the same relations, in the file's order, each line rewritten in
|
|
34
|
-
* these builders so a reader with the model open sees the same text, and
|
|
35
|
-
* so a structural compare against the file (the cloud's drift test at
|
|
36
|
-
* the re-pin) needs no translation table.
|
|
37
|
-
*
|
|
38
26
|
* The direct form carries its type restrictions (`[identity_account,
|
|
39
27
|
* organization#viewer]`) because the evaluator ENFORCES them: a tuple
|
|
40
28
|
* whose subject the line does not list is ignored, which is what OpenFGA
|
|
41
|
-
* does at write time. That is the fail-closed property of this design
|
|
29
|
+
* does at write time. That is the fail-closed property of this design:
|
|
42
30
|
* a derivation that drifts from the model (a userset the line dropped)
|
|
43
|
-
* makes
|
|
31
|
+
* makes the evaluator deny, never allow.
|
|
44
32
|
*
|
|
45
33
|
* `derived` names the relations `kind_meta.authorization` cannot derive
|
|
46
|
-
* from the row alone: `default_of` on an instance (the
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* instance
|
|
50
|
-
* (
|
|
51
|
-
*
|
|
52
|
-
*
|
|
34
|
+
* from the row alone: `default_of` on an instance (the blueprint's
|
|
35
|
+
* `status.default_instance_id` must name this row; default-of.ts, shared
|
|
36
|
+
* by the two instance kinds) and `execution_viewer` on a workflow
|
|
37
|
+
* instance (`spec.execution_visibility`; execution-viewer.ts). The
|
|
38
|
+
* binding table (bindings.ts) attaches them; the tuple source dispatches
|
|
39
|
+
* to them by relation.
|
|
40
|
+
*
|
|
41
|
+
* Only types live here: the one producer of production declarations is
|
|
42
|
+
* the reader joined with the bindings (model/index.ts). Tests build
|
|
43
|
+
* throwaway models (a cycle, a chain past the depth bound) with the
|
|
44
|
+
* builders in __tests__/throwaway-model.ts.
|
|
53
45
|
*/
|
|
54
46
|
import type { DescMessage, Message } from "@bufbuild/protobuf";
|
|
55
47
|
|
|
56
48
|
import type { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
57
49
|
|
|
58
|
-
import { kindEnumName } from "../../pipeline/apiresource-meta.js";
|
|
59
50
|
import type { ObjectRef, RowLoader, Tuple } from "../tuples.js";
|
|
60
51
|
|
|
61
52
|
/** One entry of a direct line's type restriction list. */
|
|
@@ -88,38 +79,6 @@ export type Rewrite =
|
|
|
88
79
|
readonly members: ReadonlyArray<Rewrite>;
|
|
89
80
|
};
|
|
90
81
|
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
// Builders — named so a transcript reads like its `.fga` line.
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
|
|
95
|
-
export function objectOf(type: string): SubjectType {
|
|
96
|
-
return { form: "object", type };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function usersetOf(type: string, relation: string): SubjectType {
|
|
100
|
-
return { form: "userset", type, relation };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function direct(...subjects: ReadonlyArray<SubjectType>): Rewrite {
|
|
104
|
-
return { node: "this", subjects };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function computed(relation: string): Rewrite {
|
|
108
|
-
return { node: "computed", relation };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function from(relation: string, tupleset: string): Rewrite {
|
|
112
|
-
return { node: "from", relation, tupleset };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function union(...members: ReadonlyArray<Rewrite>): Rewrite {
|
|
116
|
-
return { node: "union", members };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function intersection(...members: ReadonlyArray<Rewrite>): Rewrite {
|
|
120
|
-
return { node: "intersection", members };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
82
|
// ---------------------------------------------------------------------------
|
|
124
83
|
// A kind's declaration.
|
|
125
84
|
// ---------------------------------------------------------------------------
|
|
@@ -139,127 +98,19 @@ export type DerivedRelation = (
|
|
|
139
98
|
|
|
140
99
|
export interface KindDeclaration {
|
|
141
100
|
readonly kind: ApiResourceKind;
|
|
142
|
-
/** The FGA type name
|
|
101
|
+
/** The FGA type name: the kind's enum member name, as every edition renders objects. */
|
|
143
102
|
readonly type: string;
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
-
|
|
103
|
+
/**
|
|
104
|
+
* The kind's message schema: what a stored row decodes with, and where
|
|
105
|
+
* its audit lives. Undefined for a ROWLESS type (bindings.ts), one with
|
|
106
|
+
* no stored resource (`platform`): it resolves over tuples alone, and
|
|
107
|
+
* no row is ever loaded or listed for it.
|
|
108
|
+
*/
|
|
109
|
+
readonly schema: DescMessage | undefined;
|
|
110
|
+
/** The `.fga` file the type is defined in, relative to the server package. */
|
|
147
111
|
readonly source: string;
|
|
148
|
-
/** Every relation the
|
|
112
|
+
/** Every relation the type defines, sorted by name. */
|
|
149
113
|
readonly relations: ReadonlyMap<string, Rewrite>;
|
|
150
114
|
/** The relations `kind_meta` cannot derive (the module header). */
|
|
151
115
|
readonly derived: ReadonlyMap<string, DerivedRelation>;
|
|
152
116
|
}
|
|
153
|
-
|
|
154
|
-
export interface KindDeclarationInput {
|
|
155
|
-
readonly kind: ApiResourceKind;
|
|
156
|
-
readonly schema: DescMessage;
|
|
157
|
-
readonly source: string;
|
|
158
|
-
readonly relations: ReadonlyArray<readonly [name: string, rewrite: Rewrite]>;
|
|
159
|
-
readonly derived?: ReadonlyArray<
|
|
160
|
-
readonly [name: string, rule: DerivedRelation]
|
|
161
|
-
>;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Builds a declaration and refuses a transcript that cannot be right at
|
|
166
|
-
* module load: a relation declared twice, a `computed` naming a relation
|
|
167
|
-
* the kind does not define, a `from` whose tupleset the kind does not
|
|
168
|
-
* define (its `relation` is the PARENT's and is resolved at evaluation),
|
|
169
|
-
* a derived rule for a relation the transcript does not declare, and a
|
|
170
|
-
* line no `.fga` file could hold without parentheses (a union inside an
|
|
171
|
-
* intersection or the reverse, an operator nested in itself, an operator
|
|
172
|
-
* over fewer than two members).
|
|
173
|
-
*/
|
|
174
|
-
export function declareKind(input: KindDeclarationInput): KindDeclaration {
|
|
175
|
-
const type = kindEnumName(input.kind);
|
|
176
|
-
const relations = new Map<string, Rewrite>();
|
|
177
|
-
for (const [name, rewrite] of input.relations) {
|
|
178
|
-
if (relations.has(name)) {
|
|
179
|
-
throw new Error(`${input.source}: relation '${name}' is declared twice`);
|
|
180
|
-
}
|
|
181
|
-
const shape = unwritableShape(rewrite);
|
|
182
|
-
if (shape !== undefined) {
|
|
183
|
-
throw new Error(`${input.source}: '${name}' ${shape}`);
|
|
184
|
-
}
|
|
185
|
-
relations.set(name, rewrite);
|
|
186
|
-
}
|
|
187
|
-
for (const [name, rewrite] of relations) {
|
|
188
|
-
for (const reference of localReferences(rewrite)) {
|
|
189
|
-
if (!relations.has(reference.relation)) {
|
|
190
|
-
throw new Error(
|
|
191
|
-
`${input.source}: '${name}' names ${reference.role} '${reference.relation}', which ${type} does not declare`,
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
const derived = new Map<string, DerivedRelation>();
|
|
197
|
-
for (const [name, rule] of input.derived ?? []) {
|
|
198
|
-
if (!relations.has(name)) {
|
|
199
|
-
throw new Error(
|
|
200
|
-
`${input.source}: a derived rule for '${name}', which ${type} does not declare`,
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
derived.set(name, rule);
|
|
204
|
-
}
|
|
205
|
-
return {
|
|
206
|
-
kind: input.kind,
|
|
207
|
-
type,
|
|
208
|
-
schema: input.schema,
|
|
209
|
-
source: input.source,
|
|
210
|
-
relations,
|
|
211
|
-
derived,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/** The relations of the SAME kind a rewrite names: computed targets and tuplesets. */
|
|
216
|
-
function localReferences(
|
|
217
|
-
rewrite: Rewrite,
|
|
218
|
-
): ReadonlyArray<{ readonly role: string; readonly relation: string }> {
|
|
219
|
-
switch (rewrite.node) {
|
|
220
|
-
case "this":
|
|
221
|
-
return [];
|
|
222
|
-
case "computed":
|
|
223
|
-
return [{ role: "relation", relation: rewrite.relation }];
|
|
224
|
-
case "from":
|
|
225
|
-
return [{ role: "tupleset", relation: rewrite.tupleset }];
|
|
226
|
-
case "union":
|
|
227
|
-
case "intersection":
|
|
228
|
-
return rewrite.members.flatMap(localReferences);
|
|
229
|
-
default: {
|
|
230
|
-
const exhaustive: never = rewrite;
|
|
231
|
-
throw new Error(`unknown rewrite node: ${JSON.stringify(exhaustive)}`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Why a line could not be written in a `.fga` file without parentheses,
|
|
238
|
-
* or undefined when it can: an operator's members are leaves (a direct
|
|
239
|
-
* list, a sibling relation, a tuple-to-userset), and there are at least
|
|
240
|
-
* two of them.
|
|
241
|
-
*/
|
|
242
|
-
function unwritableShape(rewrite: Rewrite): string | undefined {
|
|
243
|
-
switch (rewrite.node) {
|
|
244
|
-
case "this":
|
|
245
|
-
case "computed":
|
|
246
|
-
case "from":
|
|
247
|
-
return undefined;
|
|
248
|
-
case "union":
|
|
249
|
-
case "intersection": {
|
|
250
|
-
if (rewrite.members.length < 2) {
|
|
251
|
-
return `is ${rewrite.node === "union" ? "an or" : "an and"} over fewer than two members`;
|
|
252
|
-
}
|
|
253
|
-
const nested = rewrite.members.find(
|
|
254
|
-
(member) => member.node === "union" || member.node === "intersection",
|
|
255
|
-
);
|
|
256
|
-
return nested === undefined
|
|
257
|
-
? undefined
|
|
258
|
-
: `nests ${nested.node === "union" ? "or" : "and"} inside ${rewrite.node === "union" ? "or" : "and"}, which a .fga line cannot say without parentheses`;
|
|
259
|
-
}
|
|
260
|
-
default: {
|
|
261
|
-
const exhaustive: never = rewrite;
|
|
262
|
-
throw new Error(`unknown rewrite node: ${JSON.stringify(exhaustive)}`);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict reads of a parsed JSON or YAML value, for the two inputs the
|
|
3
|
+
* authorization module takes as data: the compiled model
|
|
4
|
+
* (model/openfga-json.ts) and the `.fga.yaml` store tests the evaluator is
|
|
5
|
+
* proven against (__tests__/store-test-kit.ts). Both are written by a
|
|
6
|
+
* format this module does not own (OpenFGA's), so both are read as
|
|
7
|
+
* `unknown` and refused loudly on anything unexpected: a change upstream
|
|
8
|
+
* is a thrown fault naming where it failed, never a half-read input.
|
|
9
|
+
*
|
|
10
|
+
* Every function takes `where`, the path of the value in its document
|
|
11
|
+
* (`authorization-model.json: type_definitions[3].relations.viewer`), and
|
|
12
|
+
* puts it at the head of the message.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** A mapping, refusing any key outside `allowedKeys` when they are given. */
|
|
16
|
+
export function record(
|
|
17
|
+
value: unknown,
|
|
18
|
+
where: string,
|
|
19
|
+
allowedKeys?: ReadonlyArray<string>,
|
|
20
|
+
): Record<string, unknown> {
|
|
21
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
22
|
+
throw new Error(`${where}: expected a mapping`);
|
|
23
|
+
}
|
|
24
|
+
const entry = value as Record<string, unknown>;
|
|
25
|
+
if (allowedKeys !== undefined) {
|
|
26
|
+
for (const key of Object.keys(entry)) {
|
|
27
|
+
if (!allowedKeys.includes(key)) {
|
|
28
|
+
throw new Error(`${where}: unknown key '${key}'`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return entry;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function list(value: unknown, where: string): ReadonlyArray<unknown> {
|
|
36
|
+
if (!Array.isArray(value)) {
|
|
37
|
+
throw new Error(`${where}: expected a list`);
|
|
38
|
+
}
|
|
39
|
+
return value as ReadonlyArray<unknown>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A list, or the empty list when the key is absent. */
|
|
43
|
+
export function optionalList(value: unknown, where: string): ReadonlyArray<unknown> {
|
|
44
|
+
return value === undefined ? [] : list(value, where);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function text(value: unknown, where: string): string {
|
|
48
|
+
if (typeof value !== "string") {
|
|
49
|
+
throw new Error(`${where}: expected a string`);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
@@ -27,6 +27,10 @@ const PINNED: Readonly<
|
|
|
27
27
|
fingerprint:
|
|
28
28
|
"artifact{agent_execution=field:spec.source.agent_execution_id,workflow_execution=field:spec.source.workflow_execution_id}",
|
|
29
29
|
},
|
|
30
|
+
iam_policy: {
|
|
31
|
+
revision: 1,
|
|
32
|
+
fingerprint: "iam_policy{principal=field:spec.principal.id}",
|
|
33
|
+
},
|
|
30
34
|
session: {
|
|
31
35
|
revision: 1,
|
|
32
36
|
fingerprint:
|
package/src/boot/compose.ts
CHANGED
|
@@ -385,13 +385,15 @@ export async function composeServer(
|
|
|
385
385
|
requireAuthentication,
|
|
386
386
|
});
|
|
387
387
|
const builtInAuthorization = authorizationPosture !== "unit-authorizer";
|
|
388
|
-
// The built-in authorizer
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
388
|
+
// The built-in authorizer evaluates the one model every edition reads,
|
|
389
|
+
// but it derives each resource's tuples from the rows in this server's
|
|
390
|
+
// store. A wider edition's kinds are served by extensions that keep
|
|
391
|
+
// their rows in stores of their own, which the built-in tuple source
|
|
392
|
+
// cannot read, so a composition that serves a wider edition must bring
|
|
393
|
+
// its own Authorizer rather than get answers derived from rows that are
|
|
394
|
+
// not there. Refused at boot, the same class as a misconfigured
|
|
395
|
+
// registry and the verifierless posture below: caught before any side
|
|
396
|
+
// effect.
|
|
395
397
|
if (
|
|
396
398
|
authorizationPosture === "built-in" &&
|
|
397
399
|
extensions.edition !== ServerEdition.oss
|
package/src/boot/list-indexes.ts
CHANGED
|
@@ -10,9 +10,14 @@
|
|
|
10
10
|
* parent's rows without decoding the whole kind; the kinds here were
|
|
11
11
|
* chosen on the hosted edition's row counts and sizes (2026-09-23: every
|
|
12
12
|
* other org-scoped kind held under a hundred rows and a hundred kilobytes).
|
|
13
|
+
* A kind also joins when a port read on every authorization check would
|
|
14
|
+
* otherwise decode the whole kind: `iam_policy`, whose adapter reads a
|
|
15
|
+
* person's rows by principal (measured 2026-09-24: about 4.6 microseconds
|
|
16
|
+
* per row per read, 47 ms a check at ten thousand rows on sqlite).
|
|
13
17
|
*/
|
|
14
18
|
import { agentExecutionListIndex } from "../domain/agentexecution/list-index.js";
|
|
15
19
|
import { artifactListIndex } from "../domain/artifact/list-index.js";
|
|
20
|
+
import { iamPolicyListIndex } from "../domain/iampolicy/list-index.js";
|
|
16
21
|
import { sessionListIndex } from "../domain/session/list-index.js";
|
|
17
22
|
import { workflowExecutionListIndex } from "../domain/workflowexecution/list-index.js";
|
|
18
23
|
import type { ListIndexDeclaration } from "../store/list-index.js";
|
|
@@ -20,6 +25,7 @@ import type { ListIndexDeclaration } from "../store/list-index.js";
|
|
|
20
25
|
export const LIST_INDEXES: ReadonlyArray<ListIndexDeclaration> = [
|
|
21
26
|
agentExecutionListIndex,
|
|
22
27
|
artifactListIndex,
|
|
28
|
+
iamPolicyListIndex,
|
|
23
29
|
sessionListIndex,
|
|
24
30
|
workflowExecutionListIndex,
|
|
25
31
|
];
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Runs the IamPolicyStore port-contract kit (../store-contract.ts) over the
|
|
3
3
|
* OSS adapter (../resource-store.ts) on both drivers through the drivers'
|
|
4
4
|
* own fixtures (sqlite always; Postgres under TEST_DATABASE_URL), and pins
|
|
5
|
-
* the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* the two behaviours that are the OSS adapter's rather than the port's:
|
|
6
|
+
* save refuses a policy whose id is not the triple's derived id, the
|
|
7
|
+
* invariant that makes "one row per triple" the primary key's job in open
|
|
8
|
+
* source; and a principal's rows are read through the kind's list index,
|
|
9
|
+
* never by decoding the whole kind, because the built-in authorizer reads
|
|
10
|
+
* them on every check.
|
|
9
11
|
*
|
|
10
12
|
* The kit's case list is pinned by name so a case cannot drop out unnoticed:
|
|
11
13
|
* the cloud driver's test iterates the same export over `cloud.iam_policy`
|
|
@@ -21,6 +23,7 @@ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/
|
|
|
21
23
|
import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
22
24
|
import { IamPolicySchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/api_pb";
|
|
23
25
|
|
|
26
|
+
import { LIST_INDEXES } from "../../../boot/list-indexes.js";
|
|
24
27
|
import type { Store } from "../../../store/interface.js";
|
|
25
28
|
import { PostgresStore } from "../../../store/postgres/store.js";
|
|
26
29
|
import {
|
|
@@ -82,7 +85,11 @@ const postgresFixture: DriverFixture = {
|
|
|
82
85
|
if (postgresDatabase === undefined) {
|
|
83
86
|
postgresDatabase = await createTestDatabase();
|
|
84
87
|
}
|
|
85
|
-
const store = await PostgresStore.open(
|
|
88
|
+
const store = await PostgresStore.open(
|
|
89
|
+
postgresDatabase.databaseUrl,
|
|
90
|
+
undefined,
|
|
91
|
+
{ listIndexes: LIST_INDEXES },
|
|
92
|
+
);
|
|
86
93
|
await store.deleteResourcesByKind(ApiResourceKind.iam_policy);
|
|
87
94
|
return { store, close: () => store.close() };
|
|
88
95
|
},
|
|
@@ -160,6 +167,59 @@ describe.each([sqliteFixture, postgresFixture])(
|
|
|
160
167
|
spec,
|
|
161
168
|
);
|
|
162
169
|
});
|
|
170
|
+
|
|
171
|
+
it("reads a principal's rows through the list index, never by decoding the whole kind", async () => {
|
|
172
|
+
let scans = 0;
|
|
173
|
+
const target = opened.store;
|
|
174
|
+
// Every method runs on the real store; only the scan is counted.
|
|
175
|
+
const counted = new Proxy(target, {
|
|
176
|
+
get(store, property) {
|
|
177
|
+
if (property === "listResources") {
|
|
178
|
+
return (kind: ApiResourceKind) => {
|
|
179
|
+
if (kind === ApiResourceKind.iam_policy) {
|
|
180
|
+
scans += 1;
|
|
181
|
+
}
|
|
182
|
+
return store.listResources(kind);
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const value: unknown = Reflect.get(store, property, store);
|
|
186
|
+
return typeof value === "function" ? value.bind(store) : value;
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
const policies = newResourceIamPolicyStore(counted);
|
|
190
|
+
for (const [principal, relation] of [
|
|
191
|
+
["ida_wtr3jcf281yfk9xx61kj59fsme", "admin"],
|
|
192
|
+
["ida_wtr3jcf281yfk9xx61kj59fsme", "member"],
|
|
193
|
+
["ida_0hlf2yb5mhkgb3bdzkkrptqf4d", "member"],
|
|
194
|
+
] as const) {
|
|
195
|
+
const spec = orgRole(principal, relation, "acme");
|
|
196
|
+
await policies.save(
|
|
197
|
+
create(IamPolicySchema, {
|
|
198
|
+
apiVersion: IAM_POLICY_API_VERSION,
|
|
199
|
+
kind: IAM_POLICY_KIND,
|
|
200
|
+
metadata: create(ApiResourceMetadataSchema, {
|
|
201
|
+
id: policyIdFor(spec),
|
|
202
|
+
}),
|
|
203
|
+
spec,
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const found = await policies.findByPrincipal(
|
|
208
|
+
"identity_account",
|
|
209
|
+
"ida_wtr3jcf281yfk9xx61kj59fsme",
|
|
210
|
+
);
|
|
211
|
+
expect(found.map((policy) => policy.spec?.relation).sort()).toEqual([
|
|
212
|
+
"admin",
|
|
213
|
+
"member",
|
|
214
|
+
]);
|
|
215
|
+
expect(
|
|
216
|
+
await policies.findByPrincipal(
|
|
217
|
+
"team",
|
|
218
|
+
"ida_wtr3jcf281yfk9xx61kj59fsme",
|
|
219
|
+
),
|
|
220
|
+
).toEqual([]);
|
|
221
|
+
expect(scans).toBe(0);
|
|
222
|
+
});
|
|
163
223
|
});
|
|
164
224
|
},
|
|
165
225
|
);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The IamPolicy list index (store/list-index.ts): `principal` is the key
|
|
3
|
+
* the open-source adapter's `findByPrincipal` reads (resource-store.ts).
|
|
4
|
+
* It is not a lane's index: the built-in authorizer reads a person's rows,
|
|
5
|
+
* and the rows granted to each team they belong to, on every check and
|
|
6
|
+
* every list batch (authorization/derived-tuples.ts), and without the key
|
|
7
|
+
* each of those reads decodes the whole kind — a table that grows with
|
|
8
|
+
* every member, organization and grant.
|
|
9
|
+
*
|
|
10
|
+
* The key is the principal's id alone: a key reads one string field, and
|
|
11
|
+
* an id carries its kind's prefix (`ida_` for an account, `tm_` for a
|
|
12
|
+
* team), so the index narrows to one principal and the adapter's
|
|
13
|
+
* predicate on (kind, id) keeps the answer exact whatever the index
|
|
14
|
+
* returns. A policy is not organization-scoped the way a lane's rows are,
|
|
15
|
+
* so the read names no organization; the key table's lookup index leads
|
|
16
|
+
* with the key, not the organization (the v6 migration of each driver).
|
|
17
|
+
*
|
|
18
|
+
* A change to `keys` bumps `revision` (boot/__tests__/list-indexes.test.ts
|
|
19
|
+
* pins the pair).
|
|
20
|
+
*/
|
|
21
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
22
|
+
import { IamPolicySchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/api_pb";
|
|
23
|
+
|
|
24
|
+
import { declareListIndex, field } from "../../store/list-index.js";
|
|
25
|
+
|
|
26
|
+
export const iamPolicyListIndex = declareListIndex({
|
|
27
|
+
kind: ApiResourceKind.iam_policy,
|
|
28
|
+
schema: IamPolicySchema,
|
|
29
|
+
revision: 1,
|
|
30
|
+
keys: {
|
|
31
|
+
principal: field("spec.principal.id"),
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -4,14 +4,21 @@
|
|
|
4
4
|
* §3a, §4). The composition root installs it when no extension registers
|
|
5
5
|
* `drivers.iamPolicyStore`.
|
|
6
6
|
*
|
|
7
|
-
* Reads. `findById` is a PRIMARY-KEY read.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
7
|
+
* Reads. `findById` is a PRIMARY-KEY read. `findByPrincipal` is an
|
|
8
|
+
* INDEXED read through the kind's list index (list-index.ts), because the
|
|
9
|
+
* built-in authorizer calls it on every check and every list batch — for
|
|
10
|
+
* the person, and once more per team they belong to — and a scan there
|
|
11
|
+
* costs a decode of every row the server holds, per call (measured
|
|
12
|
+
* 2026-09-24: about 4.6 microseconds per row, 47 ms a check at ten
|
|
13
|
+
* thousand rows on sqlite). The index narrows by the principal's id; the
|
|
14
|
+
* (kind, id) predicate decides, so the answer is exact whatever the index
|
|
15
|
+
* returns, and the store keeps it exact while an older binary still
|
|
16
|
+
* writes (store/interface.ts, `queryResources`). Every other find decodes
|
|
17
|
+
* `listResources(iam_policy)` and filters in memory: they serve the grant
|
|
18
|
+
* path and the access lists, not the check, and the row count behind them
|
|
19
|
+
* is members × organizations on a self-host. The filters are the cloud
|
|
20
|
+
* store's WHERE clauses restated over the proto (store.ts names each), so
|
|
21
|
+
* a driver's test over either edition reads the same contract.
|
|
15
22
|
*
|
|
16
23
|
* Writes. `save` refuses a policy whose id is not its triple's derived id
|
|
17
24
|
* (constants.ts policyIdFor): open source has no legacy random ids, and a
|
|
@@ -43,6 +50,7 @@ import { kindEnumName } from "../../pipeline/apiresource-meta.js";
|
|
|
43
50
|
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
44
51
|
import type { Store } from "../../store/interface.js";
|
|
45
52
|
import { USER_GRANT_PRINCIPAL_KINDS, policyIdFor } from "./constants.js";
|
|
53
|
+
import { iamPolicyListIndex } from "./list-index.js";
|
|
46
54
|
import { DuplicatePolicyError } from "./store.js";
|
|
47
55
|
import type { IamPolicyStore } from "./store.js";
|
|
48
56
|
|
|
@@ -72,7 +80,7 @@ export function newResourceIamPolicyStore(store: Store): IamPolicyStore {
|
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
/** Every row of the kind, decoded — the scan behind every
|
|
83
|
+
/** Every row of the kind, decoded — the scan behind every read but by id and by principal. */
|
|
76
84
|
async function all(): Promise<ReadonlyArray<IamPolicy>> {
|
|
77
85
|
const rows = await store.listResources(KIND);
|
|
78
86
|
return rows.map((bytes) => fromBinary(IamPolicySchema, bytes));
|
|
@@ -102,8 +110,13 @@ export function newResourceIamPolicyStore(store: Store): IamPolicyStore {
|
|
|
102
110
|
|
|
103
111
|
findById: readById,
|
|
104
112
|
|
|
105
|
-
findByPrincipal(principalKind, principalId) {
|
|
106
|
-
|
|
113
|
+
async findByPrincipal(principalKind, principalId) {
|
|
114
|
+
const rows = await store.queryResources(iamPolicyListIndex, {
|
|
115
|
+
anyKey: [{ name: "principal", value: principalId }],
|
|
116
|
+
});
|
|
117
|
+
return rows
|
|
118
|
+
.map((row) => fromBinary(IamPolicySchema, row.data))
|
|
119
|
+
.filter((policy) => onPrincipal(policy, principalKind, principalId));
|
|
107
120
|
},
|
|
108
121
|
|
|
109
122
|
findByResource(resourceKind, resourceId) {
|
|
@@ -764,16 +764,13 @@ describe("built-in list scope (C4: two boots, one seed — the scope is the only
|
|
|
764
764
|
let unscopedStore: Store | undefined;
|
|
765
765
|
const passThrough: ListReadScope = {
|
|
766
766
|
async authorizedResourceIds(_caller, kind) {
|
|
767
|
-
const
|
|
768
|
-
if (unscopedStore === undefined ||
|
|
767
|
+
const schema = builtInModel.byKind(kind)?.schema;
|
|
768
|
+
if (unscopedStore === undefined || schema === undefined) {
|
|
769
769
|
throw new Error("the pass-through is bound to a booted server");
|
|
770
770
|
}
|
|
771
771
|
const rows = await unscopedStore.listResources(kind);
|
|
772
772
|
return new Set(
|
|
773
|
-
rows.map(
|
|
774
|
-
(bytes) =>
|
|
775
|
-
metadataOf(fromBinary(declaration.schema, bytes))?.id ?? "",
|
|
776
|
-
),
|
|
773
|
+
rows.map((bytes) => metadataOf(fromBinary(schema, bytes))?.id ?? ""),
|
|
777
774
|
);
|
|
778
775
|
},
|
|
779
776
|
restrictListEntries: (_caller, _kind, entries) =>
|