@smithers-orchestrator/gateway 0.21.0 → 0.22.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/openapi.yaml +13 -16
- package/package.json +1 -1
- package/src/auth/scopes.ts +25 -2
- package/src/rpc/index.ts +8 -3
package/openapi.yaml
CHANGED
|
@@ -2197,8 +2197,6 @@ paths:
|
|
|
2197
2197
|
type: string
|
|
2198
2198
|
-
|
|
2199
2199
|
type: number
|
|
2200
|
-
-
|
|
2201
|
-
type: integer
|
|
2202
2200
|
-
|
|
2203
2201
|
type: boolean
|
|
2204
2202
|
-
|
|
@@ -2623,8 +2621,6 @@ paths:
|
|
|
2623
2621
|
type: string
|
|
2624
2622
|
-
|
|
2625
2623
|
type: number
|
|
2626
|
-
-
|
|
2627
|
-
type: integer
|
|
2628
2624
|
-
|
|
2629
2625
|
type: boolean
|
|
2630
2626
|
-
|
|
@@ -6842,7 +6838,7 @@ paths:
|
|
|
6842
6838
|
- InvalidRequest
|
|
6843
6839
|
- Unauthorized
|
|
6844
6840
|
- Forbidden
|
|
6845
|
-
-
|
|
6841
|
+
- CronNotFound
|
|
6846
6842
|
- Internal
|
|
6847
6843
|
message:
|
|
6848
6844
|
type: string
|
|
@@ -6897,7 +6893,7 @@ paths:
|
|
|
6897
6893
|
- InvalidRequest
|
|
6898
6894
|
- Unauthorized
|
|
6899
6895
|
- Forbidden
|
|
6900
|
-
-
|
|
6896
|
+
- CronNotFound
|
|
6901
6897
|
- Internal
|
|
6902
6898
|
message:
|
|
6903
6899
|
type: string
|
|
@@ -6952,7 +6948,7 @@ paths:
|
|
|
6952
6948
|
- InvalidRequest
|
|
6953
6949
|
- Unauthorized
|
|
6954
6950
|
- Forbidden
|
|
6955
|
-
-
|
|
6951
|
+
- CronNotFound
|
|
6956
6952
|
- Internal
|
|
6957
6953
|
message:
|
|
6958
6954
|
type: string
|
|
@@ -6970,7 +6966,7 @@ paths:
|
|
|
6970
6966
|
refresh:
|
|
6971
6967
|
type: string
|
|
6972
6968
|
404:
|
|
6973
|
-
description: "The
|
|
6969
|
+
description: "The cron schedule does not exist."
|
|
6974
6970
|
content:
|
|
6975
6971
|
application/json:
|
|
6976
6972
|
schema:
|
|
@@ -7007,7 +7003,7 @@ paths:
|
|
|
7007
7003
|
- InvalidRequest
|
|
7008
7004
|
- Unauthorized
|
|
7009
7005
|
- Forbidden
|
|
7010
|
-
-
|
|
7006
|
+
- CronNotFound
|
|
7011
7007
|
- Internal
|
|
7012
7008
|
message:
|
|
7013
7009
|
type: string
|
|
@@ -7062,7 +7058,7 @@ paths:
|
|
|
7062
7058
|
- InvalidRequest
|
|
7063
7059
|
- Unauthorized
|
|
7064
7060
|
- Forbidden
|
|
7065
|
-
-
|
|
7061
|
+
- CronNotFound
|
|
7066
7062
|
- Internal
|
|
7067
7063
|
message:
|
|
7068
7064
|
type: string
|
|
@@ -7224,7 +7220,7 @@ paths:
|
|
|
7224
7220
|
- InvalidInput
|
|
7225
7221
|
- Unauthorized
|
|
7226
7222
|
- Forbidden
|
|
7227
|
-
-
|
|
7223
|
+
- CronNotFound
|
|
7228
7224
|
- Internal
|
|
7229
7225
|
message:
|
|
7230
7226
|
type: string
|
|
@@ -7280,7 +7276,7 @@ paths:
|
|
|
7280
7276
|
- InvalidInput
|
|
7281
7277
|
- Unauthorized
|
|
7282
7278
|
- Forbidden
|
|
7283
|
-
-
|
|
7279
|
+
- CronNotFound
|
|
7284
7280
|
- Internal
|
|
7285
7281
|
message:
|
|
7286
7282
|
type: string
|
|
@@ -7336,7 +7332,7 @@ paths:
|
|
|
7336
7332
|
- InvalidInput
|
|
7337
7333
|
- Unauthorized
|
|
7338
7334
|
- Forbidden
|
|
7339
|
-
-
|
|
7335
|
+
- CronNotFound
|
|
7340
7336
|
- Internal
|
|
7341
7337
|
message:
|
|
7342
7338
|
type: string
|
|
@@ -7354,7 +7350,7 @@ paths:
|
|
|
7354
7350
|
refresh:
|
|
7355
7351
|
type: string
|
|
7356
7352
|
404:
|
|
7357
|
-
description: "The
|
|
7353
|
+
description: "The cron schedule does not exist."
|
|
7358
7354
|
content:
|
|
7359
7355
|
application/json:
|
|
7360
7356
|
schema:
|
|
@@ -7392,7 +7388,7 @@ paths:
|
|
|
7392
7388
|
- InvalidInput
|
|
7393
7389
|
- Unauthorized
|
|
7394
7390
|
- Forbidden
|
|
7395
|
-
-
|
|
7391
|
+
- CronNotFound
|
|
7396
7392
|
- Internal
|
|
7397
7393
|
message:
|
|
7398
7394
|
type: string
|
|
@@ -7448,7 +7444,7 @@ paths:
|
|
|
7448
7444
|
- InvalidInput
|
|
7449
7445
|
- Unauthorized
|
|
7450
7446
|
- Forbidden
|
|
7451
|
-
-
|
|
7447
|
+
- CronNotFound
|
|
7452
7448
|
- Internal
|
|
7453
7449
|
message:
|
|
7454
7450
|
type: string
|
|
@@ -7493,6 +7489,7 @@ components:
|
|
|
7493
7489
|
- Unauthorized
|
|
7494
7490
|
- Forbidden
|
|
7495
7491
|
- RunNotFound
|
|
7492
|
+
- CronNotFound
|
|
7496
7493
|
- NodeNotFound
|
|
7497
7494
|
- IterationNotFound
|
|
7498
7495
|
- NodeHasNoOutput
|
package/package.json
CHANGED
package/src/auth/scopes.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getRequiredScopeForGatewayMethod } from "../rpc/index.ts";
|
|
2
|
+
|
|
1
3
|
export const GATEWAY_SCOPE_VALUES = [
|
|
2
4
|
"run:read",
|
|
3
5
|
"run:write",
|
|
@@ -61,6 +63,22 @@ function legacyAccessImplies(scope: string, required: GatewayScope): boolean {
|
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/**
|
|
67
|
+
* A name grant ("getRun") or wildcard-prefix grant ("runs.*") authorizes a
|
|
68
|
+
* method invocation, but it must never escalate beyond the scope that method
|
|
69
|
+
* itself requires: holding "getRun" lets you call getRun (a run:read method),
|
|
70
|
+
* not a run:admin method that happens to share the matched name/prefix. We gate
|
|
71
|
+
* the grant on the dispatched method's own required scope so a name/prefix grant
|
|
72
|
+
* can confer at most what that method legitimately needs.
|
|
73
|
+
*/
|
|
74
|
+
function methodGrantSatisfiesRequiredScope(methodName: string, requiredScope: GatewayScope): boolean {
|
|
75
|
+
const methodScope = getRequiredScopeForGatewayMethod(methodName);
|
|
76
|
+
if (methodScope === undefined) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return gatewayScopeImplies(methodScope, requiredScope);
|
|
80
|
+
}
|
|
81
|
+
|
|
64
82
|
export function hasGatewayScope(
|
|
65
83
|
grantedScopes: readonly string[],
|
|
66
84
|
requiredScope: GatewayScope,
|
|
@@ -71,10 +89,15 @@ export function hasGatewayScope(
|
|
|
71
89
|
return true;
|
|
72
90
|
}
|
|
73
91
|
for (const granted of normalized) {
|
|
74
|
-
if (methodName && granted === methodName) {
|
|
92
|
+
if (methodName && granted === methodName && methodGrantSatisfiesRequiredScope(methodName, requiredScope)) {
|
|
75
93
|
return true;
|
|
76
94
|
}
|
|
77
|
-
if (
|
|
95
|
+
if (
|
|
96
|
+
methodName &&
|
|
97
|
+
granted.endsWith(".*") &&
|
|
98
|
+
methodName.startsWith(granted.slice(0, -1)) &&
|
|
99
|
+
methodGrantSatisfiesRequiredScope(methodName, requiredScope)
|
|
100
|
+
) {
|
|
78
101
|
return true;
|
|
79
102
|
}
|
|
80
103
|
if (isGatewayScope(granted) && gatewayScopeImplies(granted, requiredScope)) {
|
package/src/rpc/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ export type GatewayRpcErrorCode =
|
|
|
30
30
|
| "Unauthorized"
|
|
31
31
|
| "Forbidden"
|
|
32
32
|
| "RunNotFound"
|
|
33
|
+
| "CronNotFound"
|
|
33
34
|
| "NodeNotFound"
|
|
34
35
|
| "IterationNotFound"
|
|
35
36
|
| "NodeHasNoOutput"
|
|
@@ -288,6 +289,10 @@ const arraySchema = (items: JsonSchema, description: string): JsonSchema => ({
|
|
|
288
289
|
});
|
|
289
290
|
|
|
290
291
|
export const anyJsonSchema: JsonSchema = {
|
|
292
|
+
// The branches are mutually exclusive so a value matches exactly one of them
|
|
293
|
+
// under strict `oneOf` semantics. The `number` branch already covers integers
|
|
294
|
+
// (an integer is a JSON number), so a separate `integer` branch would make
|
|
295
|
+
// every integer match two branches and fail `oneOf` validation.
|
|
291
296
|
description: "Any JSON value.",
|
|
292
297
|
nullable: true,
|
|
293
298
|
oneOf: [
|
|
@@ -295,7 +300,6 @@ export const anyJsonSchema: JsonSchema = {
|
|
|
295
300
|
{ type: "array", items: { nullable: true } },
|
|
296
301
|
{ type: "string" },
|
|
297
302
|
{ type: "number" },
|
|
298
|
-
{ type: "integer" },
|
|
299
303
|
{ type: "boolean" },
|
|
300
304
|
{ type: "null" },
|
|
301
305
|
],
|
|
@@ -324,6 +328,7 @@ export const GATEWAY_RPC_ERRORS: Record<GatewayRpcErrorCode, GatewayRpcErrorDefi
|
|
|
324
328
|
Unauthorized: { version: SMITHERS_API_VERSION, code: "Unauthorized", httpStatus: 401, description: "Authentication failed or the token expired." },
|
|
325
329
|
Forbidden: { version: SMITHERS_API_VERSION, code: "Forbidden", httpStatus: 403, description: "The token is missing the required scope." },
|
|
326
330
|
RunNotFound: { version: SMITHERS_API_VERSION, code: "RunNotFound", httpStatus: 404, description: "The run does not exist." },
|
|
331
|
+
CronNotFound: { version: SMITHERS_API_VERSION, code: "CronNotFound", httpStatus: 404, description: "The cron schedule does not exist." },
|
|
327
332
|
NodeNotFound: { version: SMITHERS_API_VERSION, code: "NodeNotFound", httpStatus: 404, description: "The node does not exist on the run." },
|
|
328
333
|
IterationNotFound: { version: SMITHERS_API_VERSION, code: "IterationNotFound", httpStatus: 404, description: "The requested node iteration does not exist." },
|
|
329
334
|
NodeHasNoOutput: { version: SMITHERS_API_VERSION, code: "NodeHasNoOutput", httpStatus: 404, description: "The node has not produced output." },
|
|
@@ -637,7 +642,7 @@ export const GATEWAY_RPC_DEFINITIONS: readonly GatewayRpcDefinition[] = [
|
|
|
637
642
|
requiredScope: "cron:write",
|
|
638
643
|
requestSchema: objectSchema({ cronId: stringSchema("Cron id.") }, ["cronId"]),
|
|
639
644
|
responseSchema: objectSchema({ cronId: stringSchema("Cron id."), removed: booleanSchema("True when removed.") }, ["cronId", "removed"]),
|
|
640
|
-
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "
|
|
645
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "CronNotFound", "Internal"],
|
|
641
646
|
exampleRequest: { cronId: "cron_01" },
|
|
642
647
|
exampleResponse: { cronId: "cron_01", removed: true },
|
|
643
648
|
},
|
|
@@ -651,7 +656,7 @@ export const GATEWAY_RPC_DEFINITIONS: readonly GatewayRpcDefinition[] = [
|
|
|
651
656
|
requiredScope: "cron:write",
|
|
652
657
|
requestSchema: objectSchema({ cronId: stringSchema("Cron id."), workflow, input: objectSchema({}, [], "Workflow input.", true) }),
|
|
653
658
|
responseSchema: objectSchema({ runId, workflow }, ["runId", "workflow"]),
|
|
654
|
-
errors: ["InvalidRequest", "InvalidInput", "Unauthorized", "Forbidden", "
|
|
659
|
+
errors: ["InvalidRequest", "InvalidInput", "Unauthorized", "Forbidden", "CronNotFound", "Internal"],
|
|
655
660
|
exampleRequest: { cronId: "cron_01", input: { dryRun: true } },
|
|
656
661
|
exampleResponse: { runId: "run_02", workflow: "deploy" },
|
|
657
662
|
},
|