@stigmer/server 3.27.2 → 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 +3 -3
- package/dist/authorization/authorizer.js +3 -3
- package/dist/authorization/derived-tuples.js +6 -1
- 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/domain/workflowexecution/list-index.d.ts +1 -1
- package/dist/extensions/list-read-scope.d.ts +18 -2
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +20 -1
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/authorization/README.md +41 -34
- package/src/authorization/__tests__/authorizer.test.ts +5 -14
- package/src/authorization/__tests__/derived-tuples.test.ts +29 -27
- 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 +12 -20
- 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 +3 -3
- package/src/authorization/derived-tuples.ts +7 -2
- 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/compose.ts +9 -7
- package/src/extensions/__tests__/built-in-authorization-composed.test.ts +3 -6
- package/src/extensions/list-read-scope.ts +30 -3
- package/src/index.ts +15 -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__/enterprise-model.ts +0 -59
- 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,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pins the evaluator's semantics the store tests cannot express (the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
3
|
+
* model's `.fga.yaml` suites prove its answers; this file proves the
|
|
4
|
+
* machinery under them): how a person matches a tuple, that a direct
|
|
5
|
+
* tuple outside the line's type restriction is ignored (model drift fails
|
|
6
|
+
* closed), how far a resolution may walk, that an intersection holds only
|
|
7
|
+
* while every member does (the team bound) and stops at its first false
|
|
8
|
+
* member, and that a declaration cycle or an undeclared target is a
|
|
9
|
+
* thrown fault and never a silent deny. What the model file may hold is
|
|
10
|
+
* the reader's to refuse (model/__tests__/openfga-json.test.ts).
|
|
11
11
|
*
|
|
12
12
|
* The fixture source is the in-memory one the store-test kit uses, so the
|
|
13
13
|
* two proofs share one tuple vocabulary. Where a case needs a shape the
|
|
14
|
-
* real model does not have (a cycle, a chain past the depth bound
|
|
15
|
-
* declares a throwaway model
|
|
16
|
-
* over the built-in model so the fixtures are real
|
|
14
|
+
* real model does not have (a cycle, a chain past the depth bound, a type
|
|
15
|
+
* the model omits), it declares a throwaway model (throwaway-model.ts);
|
|
16
|
+
* everything else runs over the built-in model so the fixtures are real
|
|
17
|
+
* relations.
|
|
17
18
|
*/
|
|
18
19
|
import { describe, expect, it } from "vitest";
|
|
19
20
|
|
|
@@ -26,23 +27,21 @@ import {
|
|
|
26
27
|
checkRelation,
|
|
27
28
|
} from "../evaluator.js";
|
|
28
29
|
import { builtInModel, newModel } from "../model/index.js";
|
|
30
|
+
import type { Person, Tuple, TupleSource } from "../tuples.js";
|
|
31
|
+
import {
|
|
32
|
+
newInMemoryTupleSource,
|
|
33
|
+
parseObjectRef,
|
|
34
|
+
parseSubject,
|
|
35
|
+
} from "../tuples.js";
|
|
29
36
|
import {
|
|
30
37
|
computed,
|
|
31
|
-
declareKind,
|
|
32
38
|
direct,
|
|
33
39
|
from,
|
|
34
|
-
intersection,
|
|
35
40
|
objectOf,
|
|
41
|
+
throwawayDeclaration,
|
|
36
42
|
union,
|
|
37
43
|
usersetOf,
|
|
38
|
-
} from "
|
|
39
|
-
import type { Rewrite } from "../model/rewrite.js";
|
|
40
|
-
import type { Person, Tuple, TupleSource } from "../tuples.js";
|
|
41
|
-
import {
|
|
42
|
-
newInMemoryTupleSource,
|
|
43
|
-
parseObjectRef,
|
|
44
|
-
parseSubject,
|
|
45
|
-
} from "../tuples.js";
|
|
44
|
+
} from "./throwaway-model.js";
|
|
46
45
|
|
|
47
46
|
/** `object#relation@subject` in the FGA tuple notation, the fixtures' own spelling. */
|
|
48
47
|
function tuple(text: string): Tuple {
|
|
@@ -157,13 +156,13 @@ describe("relations and kinds the model does not declare", () => {
|
|
|
157
156
|
).toBe(false);
|
|
158
157
|
});
|
|
159
158
|
|
|
160
|
-
it("an undeclared kind as the TARGET is a thrown fault — the
|
|
159
|
+
it("an undeclared kind as the TARGET is a thrown fault — the model declares every kind, so reaching here is a registry gap", async () => {
|
|
161
160
|
await expect(
|
|
162
|
-
allowed(source, "
|
|
161
|
+
allowed(source, "api_resource_version:v1", "can_view", person("ida_carol")),
|
|
163
162
|
).rejects.toMatchObject({ reason: "undeclared-target-kind" });
|
|
164
163
|
});
|
|
165
164
|
|
|
166
|
-
it("
|
|
165
|
+
it("a declared kind reached through a hop with no tuple on it yields nothing — `agent.platform_viewer` walks `identity_provider#platform_user`", async () => {
|
|
167
166
|
const platform = newInMemoryTupleSource([
|
|
168
167
|
tuple("agent:p#organization@organization:acme"),
|
|
169
168
|
tuple("agent:p#platform_viewer@identity_provider:idp#platform_user"),
|
|
@@ -172,6 +171,30 @@ describe("relations and kinds the model does not declare", () => {
|
|
|
172
171
|
await allowed(platform, "agent:p", "can_view", person("ida_zed")),
|
|
173
172
|
).toBe(false);
|
|
174
173
|
});
|
|
174
|
+
|
|
175
|
+
it("a type the model omits, reached through a hop, is an object with no relations — nothing, never a fault", async () => {
|
|
176
|
+
const model = newModel([
|
|
177
|
+
throwawayDeclaration({
|
|
178
|
+
kind: ApiResourceKind.agent,
|
|
179
|
+
schema: AgentSchema,
|
|
180
|
+
relations: [
|
|
181
|
+
["viewer", direct(usersetOf("api_resource_version", "member"))],
|
|
182
|
+
["can_view", computed("viewer")],
|
|
183
|
+
],
|
|
184
|
+
}),
|
|
185
|
+
]);
|
|
186
|
+
const hop = newInMemoryTupleSource([
|
|
187
|
+
tuple("agent:p#viewer@api_resource_version:v1#member"),
|
|
188
|
+
]);
|
|
189
|
+
expect(
|
|
190
|
+
await checkRelation(
|
|
191
|
+
{ model, source: hop },
|
|
192
|
+
parseObjectRef("agent:p"),
|
|
193
|
+
"can_view",
|
|
194
|
+
person("ida_zed"),
|
|
195
|
+
),
|
|
196
|
+
).toBe(false);
|
|
197
|
+
});
|
|
175
198
|
});
|
|
176
199
|
|
|
177
200
|
describe("the walk itself", () => {
|
|
@@ -203,10 +226,9 @@ describe("the walk itself", () => {
|
|
|
203
226
|
|
|
204
227
|
it("bounds the resolution at OpenFGA's own limit and reports the overrun as a fault", async () => {
|
|
205
228
|
// A chain of `next from link` deeper than the bound, on a throwaway model.
|
|
206
|
-
const deep =
|
|
229
|
+
const deep = throwawayDeclaration({
|
|
207
230
|
kind: ApiResourceKind.agent,
|
|
208
231
|
schema: AgentSchema,
|
|
209
|
-
source: "test/deep.fga",
|
|
210
232
|
relations: [
|
|
211
233
|
["link", direct(objectOf("agent"))],
|
|
212
234
|
[
|
|
@@ -230,23 +252,8 @@ describe("the walk itself", () => {
|
|
|
230
252
|
});
|
|
231
253
|
|
|
232
254
|
it("an intersection holds only while every member does — a team member reaches a team grant only while still an organization viewer", async () => {
|
|
233
|
-
// The Enterprise team type
|
|
234
|
-
//
|
|
235
|
-
const team = declareKind({
|
|
236
|
-
kind: ApiResourceKind.team,
|
|
237
|
-
schema: AgentSchema,
|
|
238
|
-
source: "test/team.fga",
|
|
239
|
-
relations: [
|
|
240
|
-
["organization", direct(objectOf("organization"))],
|
|
241
|
-
[
|
|
242
|
-
"member",
|
|
243
|
-
intersection(
|
|
244
|
-
direct(objectOf("identity_account")),
|
|
245
|
-
from("viewer", "organization"),
|
|
246
|
-
),
|
|
247
|
-
],
|
|
248
|
-
],
|
|
249
|
-
});
|
|
255
|
+
// The model's Enterprise team type: `member` is a person granted the
|
|
256
|
+
// role AND still one of the organization's viewers.
|
|
250
257
|
const source = newInMemoryTupleSource([
|
|
251
258
|
tuple("organization:acme#viewer@identity_account:ida_vic"),
|
|
252
259
|
tuple("team:sre#organization@organization:acme"),
|
|
@@ -255,10 +262,9 @@ describe("the walk itself", () => {
|
|
|
255
262
|
tuple("agent:a#organization@organization:acme"),
|
|
256
263
|
tuple("agent:a#viewer@team:sre#member"),
|
|
257
264
|
]);
|
|
258
|
-
const model = newModel([...builtInModel.declarations, team]);
|
|
259
265
|
const check = (who: Person): Promise<boolean> =>
|
|
260
266
|
checkRelation(
|
|
261
|
-
{ model, source },
|
|
267
|
+
{ model: builtInModel, source },
|
|
262
268
|
parseObjectRef("agent:a"),
|
|
263
269
|
"can_view",
|
|
264
270
|
who,
|
|
@@ -270,21 +276,6 @@ describe("the walk itself", () => {
|
|
|
270
276
|
});
|
|
271
277
|
|
|
272
278
|
it("an intersection stops at its first false member — the direct arm is read before the hop that bounds it", async () => {
|
|
273
|
-
const team = declareKind({
|
|
274
|
-
kind: ApiResourceKind.team,
|
|
275
|
-
schema: AgentSchema,
|
|
276
|
-
source: "test/team.fga",
|
|
277
|
-
relations: [
|
|
278
|
-
["organization", direct(objectOf("organization"))],
|
|
279
|
-
[
|
|
280
|
-
"member",
|
|
281
|
-
intersection(
|
|
282
|
-
direct(objectOf("identity_account")),
|
|
283
|
-
from("viewer", "organization"),
|
|
284
|
-
),
|
|
285
|
-
],
|
|
286
|
-
],
|
|
287
|
-
});
|
|
288
279
|
const asked: string[] = [];
|
|
289
280
|
const inner = newInMemoryTupleSource([
|
|
290
281
|
tuple("team:sre#organization@organization:acme"),
|
|
@@ -297,7 +288,7 @@ describe("the walk itself", () => {
|
|
|
297
288
|
};
|
|
298
289
|
expect(
|
|
299
290
|
await checkRelation(
|
|
300
|
-
{ model:
|
|
291
|
+
{ model: builtInModel, source: counting },
|
|
301
292
|
parseObjectRef("team:sre"),
|
|
302
293
|
"member",
|
|
303
294
|
person("ida_nobody"),
|
|
@@ -307,10 +298,9 @@ describe("the walk itself", () => {
|
|
|
307
298
|
});
|
|
308
299
|
|
|
309
300
|
it("a declaration that re-enters itself on one object is a cycle fault, never a silent false", async () => {
|
|
310
|
-
const cyclic =
|
|
301
|
+
const cyclic = throwawayDeclaration({
|
|
311
302
|
kind: ApiResourceKind.agent,
|
|
312
303
|
schema: AgentSchema,
|
|
313
|
-
source: "test/cyclic.fga",
|
|
314
304
|
relations: [
|
|
315
305
|
["a", computed("b")],
|
|
316
306
|
["b", computed("a")],
|
|
@@ -326,100 +316,3 @@ describe("the walk itself", () => {
|
|
|
326
316
|
).rejects.toBeInstanceOf(AuthorizationEvaluationError);
|
|
327
317
|
});
|
|
328
318
|
});
|
|
329
|
-
|
|
330
|
-
describe("declareKind validates a transcript at load", () => {
|
|
331
|
-
it("refuses a computed or tupleset reference to a relation the kind does not declare", () => {
|
|
332
|
-
expect(() =>
|
|
333
|
-
declareKind({
|
|
334
|
-
kind: ApiResourceKind.agent,
|
|
335
|
-
schema: AgentSchema,
|
|
336
|
-
source: "test/bad.fga",
|
|
337
|
-
relations: [["can_view", computed("viewer")]],
|
|
338
|
-
}),
|
|
339
|
-
).toThrow("relation 'viewer'");
|
|
340
|
-
expect(() =>
|
|
341
|
-
declareKind({
|
|
342
|
-
kind: ApiResourceKind.agent,
|
|
343
|
-
schema: AgentSchema,
|
|
344
|
-
source: "test/bad.fga",
|
|
345
|
-
relations: [["owner", from("admin", "organization")]],
|
|
346
|
-
}),
|
|
347
|
-
).toThrow("tupleset 'organization'");
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
it("refuses a relation declared twice", () => {
|
|
351
|
-
expect(() =>
|
|
352
|
-
declareKind({
|
|
353
|
-
kind: ApiResourceKind.agent,
|
|
354
|
-
schema: AgentSchema,
|
|
355
|
-
source: "test/bad.fga",
|
|
356
|
-
relations: [
|
|
357
|
-
["viewer", direct(objectOf("identity_account"))],
|
|
358
|
-
["viewer", direct(usersetOf("organization", "viewer"))],
|
|
359
|
-
],
|
|
360
|
-
}),
|
|
361
|
-
).toThrow("declared twice");
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it("refuses a line no .fga file could hold without parentheses, and an operator over one member", () => {
|
|
365
|
-
const declare = (rewrite: Rewrite) =>
|
|
366
|
-
declareKind({
|
|
367
|
-
kind: ApiResourceKind.agent,
|
|
368
|
-
schema: AgentSchema,
|
|
369
|
-
source: "test/bad.fga",
|
|
370
|
-
relations: [
|
|
371
|
-
["organization", direct(objectOf("organization"))],
|
|
372
|
-
["viewer", rewrite],
|
|
373
|
-
],
|
|
374
|
-
});
|
|
375
|
-
expect(() =>
|
|
376
|
-
declare(
|
|
377
|
-
intersection(
|
|
378
|
-
union(direct(objectOf("identity_account")), computed("organization")),
|
|
379
|
-
from("viewer", "organization"),
|
|
380
|
-
),
|
|
381
|
-
),
|
|
382
|
-
).toThrow("nests or inside and");
|
|
383
|
-
expect(() =>
|
|
384
|
-
declare(
|
|
385
|
-
union(
|
|
386
|
-
intersection(
|
|
387
|
-
direct(objectOf("identity_account")),
|
|
388
|
-
from("viewer", "organization"),
|
|
389
|
-
),
|
|
390
|
-
computed("organization"),
|
|
391
|
-
),
|
|
392
|
-
),
|
|
393
|
-
).toThrow("nests and inside or");
|
|
394
|
-
expect(() =>
|
|
395
|
-
declare(intersection(direct(objectOf("identity_account")))),
|
|
396
|
-
).toThrow("fewer than two members");
|
|
397
|
-
expect(() =>
|
|
398
|
-
declare(
|
|
399
|
-
intersection(
|
|
400
|
-
direct(objectOf("identity_account")),
|
|
401
|
-
from("viewer", "organization"),
|
|
402
|
-
),
|
|
403
|
-
),
|
|
404
|
-
).not.toThrow();
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
it("carries the FGA type name of its kind and its relations in file order", () => {
|
|
408
|
-
const declaration = declareKind({
|
|
409
|
-
kind: ApiResourceKind.mcp_server,
|
|
410
|
-
schema: AgentSchema,
|
|
411
|
-
source: "test/order.fga",
|
|
412
|
-
relations: [
|
|
413
|
-
["organization", direct(objectOf("organization"))],
|
|
414
|
-
["owner", direct(usersetOf("organization", "admin"))],
|
|
415
|
-
["can_edit", computed("owner")],
|
|
416
|
-
],
|
|
417
|
-
});
|
|
418
|
-
expect(declaration.type).toBe("mcp_server");
|
|
419
|
-
expect([...declaration.relations.keys()]).toEqual([
|
|
420
|
-
"organization",
|
|
421
|
-
"owner",
|
|
422
|
-
"can_edit",
|
|
423
|
-
]);
|
|
424
|
-
});
|
|
425
|
-
});
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
} from "../../pipeline/steps/authorization-tuples.js";
|
|
37
37
|
import { rowFactsOf, rowFactsOfEntry } from "../facts.js";
|
|
38
38
|
import { builtInModel } from "../model/index.js";
|
|
39
|
-
import { fixtureRow } from "./support.js";
|
|
39
|
+
import { fixtureRow, hasStoredRows, storedDeclaration } from "./support.js";
|
|
40
40
|
|
|
41
41
|
const CREATOR: CallerIdentity = {
|
|
42
42
|
identityId: "ida_carol",
|
|
@@ -46,7 +46,7 @@ const CREATOR: CallerIdentity = {
|
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
describe("rowFactsOf against the tuple lifecycle's own resolution", () => {
|
|
49
|
-
for (const declaration of builtInModel.declarations) {
|
|
49
|
+
for (const declaration of builtInModel.declarations.filter(hasStoredRows)) {
|
|
50
50
|
for (const visibility of [
|
|
51
51
|
ApiResourceVisibility.visibility_private,
|
|
52
52
|
ApiResourceVisibility.visibility_org,
|
|
@@ -77,10 +77,7 @@ describe("rowFactsOf against the tuple lifecycle's own resolution", () => {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
it("carries a legacy row's missing organization as no scope link, where the create-time resolver throws", () => {
|
|
80
|
-
const agent =
|
|
81
|
-
if (agent === undefined) {
|
|
82
|
-
throw new Error("agent is declared in this slice");
|
|
83
|
-
}
|
|
80
|
+
const agent = storedDeclaration("agent");
|
|
84
81
|
const row = fixtureRow(agent, {
|
|
85
82
|
id: "agent-legacy",
|
|
86
83
|
org: "",
|
|
@@ -94,10 +91,7 @@ describe("rowFactsOf against the tuple lifecycle's own resolution", () => {
|
|
|
94
91
|
});
|
|
95
92
|
|
|
96
93
|
it("reads an absent creator stamp as the empty string", () => {
|
|
97
|
-
const agent =
|
|
98
|
-
if (agent === undefined) {
|
|
99
|
-
throw new Error("agent is declared in this slice");
|
|
100
|
-
}
|
|
94
|
+
const agent = storedDeclaration("agent");
|
|
101
95
|
const bare = create(agent.schema, {
|
|
102
96
|
metadata: { id: "agent-bare", org: "acme" },
|
|
103
97
|
});
|
|
@@ -129,7 +123,7 @@ describe("a list candidate against the loaded row — the facts' two producers",
|
|
|
129
123
|
return offered;
|
|
130
124
|
}
|
|
131
125
|
|
|
132
|
-
for (const declaration of builtInModel.declarations) {
|
|
126
|
+
for (const declaration of builtInModel.declarations.filter(hasStoredRows)) {
|
|
133
127
|
for (const visibility of [
|
|
134
128
|
ApiResourceVisibility.visibility_private,
|
|
135
129
|
ApiResourceVisibility.visibility_org,
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
*
|
|
49
49
|
* Every shape states what its caller keeps, so a measurement over an
|
|
50
50
|
* evaluator that answered wrongly measures nothing. The model is the
|
|
51
|
-
* built-in one
|
|
52
|
-
*
|
|
51
|
+
* built-in one, which declares the Enterprise team type beside the
|
|
52
|
+
* open-source ones.
|
|
53
53
|
*/
|
|
54
54
|
import { create } from "@bufbuild/protobuf";
|
|
55
55
|
import { afterAll, afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
@@ -84,8 +84,8 @@ import { newBuiltInListReadScope } from "../list-read-scope.js";
|
|
|
84
84
|
import type { KindDeclaration } from "../model/rewrite.js";
|
|
85
85
|
import { driverFixtures, dropPostgresFixture } from "./drivers.js";
|
|
86
86
|
import type { OpenedStore } from "./drivers.js";
|
|
87
|
-
import {
|
|
88
|
-
import { fixtureRow } from "./support.js";
|
|
87
|
+
import { builtInModel } from "../model/index.js";
|
|
88
|
+
import { fixtureRow, storedDeclaration } from "./support.js";
|
|
89
89
|
import type { FixtureRowFacts } from "./support.js";
|
|
90
90
|
|
|
91
91
|
const MEASURE = process.env["AUTHORIZATION_MEASURE"] === "1";
|
|
@@ -143,14 +143,6 @@ function directGrantOn(i: number): boolean {
|
|
|
143
143
|
return i % 50 === 1;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
function declared(type: string): KindDeclaration {
|
|
147
|
-
const declaration = enterpriseModel.byType(type);
|
|
148
|
-
if (declaration === undefined) {
|
|
149
|
-
throw new Error(`${type} is declared`);
|
|
150
|
-
}
|
|
151
|
-
return declaration;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
146
|
function resolved(accountId: string): CallerIdentity {
|
|
155
147
|
return {
|
|
156
148
|
identityId: accountId,
|
|
@@ -461,7 +453,7 @@ describe
|
|
|
461
453
|
let accounts: IdentityAccountStore;
|
|
462
454
|
|
|
463
455
|
async function save(type: string, facts: FixtureRowFacts): Promise<object> {
|
|
464
|
-
const declaration =
|
|
456
|
+
const declaration = storedDeclaration(type);
|
|
465
457
|
const row = fixtureRow(declaration, facts);
|
|
466
458
|
await opened.store.saveResource(
|
|
467
459
|
declaration.kind,
|
|
@@ -530,8 +522,8 @@ describe
|
|
|
530
522
|
await opened.store.saveResource(
|
|
531
523
|
ApiResourceKind.team,
|
|
532
524
|
teamId(team),
|
|
533
|
-
|
|
534
|
-
fixtureRow(
|
|
525
|
+
storedDeclaration("team").schema,
|
|
526
|
+
fixtureRow(storedDeclaration("team"), {
|
|
535
527
|
id: teamId(team),
|
|
536
528
|
org: ORG,
|
|
537
529
|
visibility: ApiResourceVisibility.visibility_private,
|
|
@@ -550,7 +542,7 @@ describe
|
|
|
550
542
|
it(
|
|
551
543
|
`${shape.name} at ${n} rows`,
|
|
552
544
|
async () => {
|
|
553
|
-
const declaration =
|
|
545
|
+
const declaration = storedDeclaration(shape.kind);
|
|
554
546
|
const policyRows = await seedPopulation(n);
|
|
555
547
|
for (const parent of shape.parents ?? []) {
|
|
556
548
|
await save(parent.type, parent.facts);
|
|
@@ -568,7 +560,7 @@ describe
|
|
|
568
560
|
policies,
|
|
569
561
|
accounts,
|
|
570
562
|
logger: silentLogger,
|
|
571
|
-
model:
|
|
563
|
+
model: builtInModel,
|
|
572
564
|
});
|
|
573
565
|
const started = performance.now();
|
|
574
566
|
const kept =
|
|
@@ -599,7 +591,7 @@ describe
|
|
|
599
591
|
it(
|
|
600
592
|
`pages: ${shape.name}, at a ${PAGE_POPULATION}-row population`,
|
|
601
593
|
async () => {
|
|
602
|
-
const declaration =
|
|
594
|
+
const declaration = storedDeclaration("agent");
|
|
603
595
|
const policyRows = await seedPopulation(PAGE_POPULATION);
|
|
604
596
|
const entries: ListEntryMeta[] = [];
|
|
605
597
|
for (let i = 0; i < PAGE_EXAMINE_BUDGET; i += 1) {
|
|
@@ -614,7 +606,7 @@ describe
|
|
|
614
606
|
policies,
|
|
615
607
|
accounts,
|
|
616
608
|
logger: silentLogger,
|
|
617
|
-
model:
|
|
609
|
+
model: builtInModel,
|
|
618
610
|
});
|
|
619
611
|
const caller = resolved(shape.caller);
|
|
620
612
|
// A page's batches run one after another, each through the scope
|
|
@@ -659,7 +651,7 @@ describe
|
|
|
659
651
|
accounts,
|
|
660
652
|
edition: ServerEdition.oss,
|
|
661
653
|
logger: silentLogger,
|
|
662
|
-
model:
|
|
654
|
+
model: builtInModel,
|
|
663
655
|
});
|
|
664
656
|
const check = {
|
|
665
657
|
permission: IamPermission.can_view,
|
|
@@ -53,7 +53,10 @@ import type {
|
|
|
53
53
|
ListEntryMeta,
|
|
54
54
|
ListReadScope,
|
|
55
55
|
} from "../../extensions/list-read-scope.js";
|
|
56
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
InternalCallerOfferedError,
|
|
58
|
+
restrictListByReadScope,
|
|
59
|
+
} from "../../extensions/list-read-scope.js";
|
|
57
60
|
import { kindEnumName } from "../../pipeline/apiresource-meta.js";
|
|
58
61
|
import { rowAuthorizationFactsOf } from "../../pipeline/steps/authorization-facts.js";
|
|
59
62
|
import type { Store } from "../../store/interface.js";
|
|
@@ -63,7 +66,7 @@ import { builtInModel } from "../model/index.js";
|
|
|
63
66
|
import type { KindDeclaration } from "../model/rewrite.js";
|
|
64
67
|
import { driverFixtures, dropPostgresFixture } from "./drivers.js";
|
|
65
68
|
import type { OpenedStore } from "./drivers.js";
|
|
66
|
-
import { fixtureRow } from "./support.js";
|
|
69
|
+
import { fixtureRow, storedDeclaration } from "./support.js";
|
|
67
70
|
import type { FixtureRowFacts } from "./support.js";
|
|
68
71
|
|
|
69
72
|
const FOUNDER = accountIdFor("auth0|founder");
|
|
@@ -94,14 +97,6 @@ const SEEDED_KINDS = [
|
|
|
94
97
|
ApiResourceKind.agent_instance,
|
|
95
98
|
];
|
|
96
99
|
|
|
97
|
-
function declared(type: string): KindDeclaration {
|
|
98
|
-
const declaration = builtInModel.byType(type);
|
|
99
|
-
if (declaration === undefined) {
|
|
100
|
-
throw new Error(`${type} is declared`);
|
|
101
|
-
}
|
|
102
|
-
return declaration;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
100
|
function resolved(accountId: string): CallerIdentity {
|
|
106
101
|
return {
|
|
107
102
|
identityId: accountId,
|
|
@@ -161,7 +156,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
161
156
|
type: string,
|
|
162
157
|
facts: FixtureRowFacts,
|
|
163
158
|
): Promise<void> {
|
|
164
|
-
const declaration =
|
|
159
|
+
const declaration = storedDeclaration(type);
|
|
165
160
|
const row = fixtureRow(declaration, facts);
|
|
166
161
|
await opened.store.saveResource(
|
|
167
162
|
declaration.kind,
|
|
@@ -608,9 +603,15 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
608
603
|
() => undefined,
|
|
609
604
|
(error: unknown) => error,
|
|
610
605
|
);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
606
|
+
// The seam's own refusal, the one a composition's driver throws
|
|
607
|
+
// too; never the evaluator's fault, since nothing was evaluated.
|
|
608
|
+
expect(refusal).toBeInstanceOf(InternalCallerOfferedError);
|
|
609
|
+
expect(refusal).not.toBeInstanceOf(AuthorizationEvaluationError);
|
|
610
|
+
expect((refusal as InternalCallerOfferedError).kind).toBe(
|
|
611
|
+
ApiResourceKind.agent,
|
|
612
|
+
);
|
|
613
|
+
expect((refusal as InternalCallerOfferedError).message).toContain(
|
|
614
|
+
"kind 'agent'",
|
|
614
615
|
);
|
|
615
616
|
// Refused before any read: the server was never evaluated as a person.
|
|
616
617
|
expect(accountReads).toBe(0);
|
|
@@ -38,7 +38,7 @@ import { builtInModel } from "../model/index.js";
|
|
|
38
38
|
import { newBuiltInOrganizationDirectory } from "../organization-directory.js";
|
|
39
39
|
import { driverFixtures, dropPostgresFixture } from "./drivers.js";
|
|
40
40
|
import type { OpenedStore } from "./drivers.js";
|
|
41
|
-
import { fixtureRow } from "./support.js";
|
|
41
|
+
import { fixtureRow, storedDeclaration } from "./support.js";
|
|
42
42
|
|
|
43
43
|
const FOUNDER = accountIdFor("auth0|founder");
|
|
44
44
|
const MEMBER = accountIdFor("auth0|member");
|
|
@@ -81,7 +81,7 @@ describe.each(
|
|
|
81
81
|
let principalReads: number;
|
|
82
82
|
|
|
83
83
|
async function organization(id: string) {
|
|
84
|
-
const declaration =
|
|
84
|
+
const declaration = storedDeclaration("organization");
|
|
85
85
|
await opened.store.saveResource(
|
|
86
86
|
ApiResourceKind.organization,
|
|
87
87
|
id,
|