@actiondock/core 2.18.0 → 2.20.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/dist/client/config.d.ts +17 -14
- package/dist/client/config.js +112 -14
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/runs.d.ts +9 -0
- package/dist/client/runs.js +7 -0
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js +2 -0
- package/dist/execution/service.js +8 -5
- package/dist/execution/types.d.ts +3 -3
- package/dist/host/host.d.ts +9 -1
- package/dist/host/host.js +266 -45
- package/dist/host/types.d.ts +3 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/input/config-override.d.ts +5 -0
- package/dist/input/config-override.js +23 -0
- package/dist/input/input-resolver.d.ts +24 -7
- package/dist/input/input-resolver.js +73 -5
- package/dist/invocation/bindings.d.ts +53 -0
- package/dist/invocation/bindings.js +248 -0
- package/dist/invocation/policy.d.ts +5 -1
- package/dist/invocation/policy.js +16 -9
- package/dist/invocation/remote.d.ts +40 -0
- package/dist/invocation/remote.js +489 -0
- package/dist/invocation/types.d.ts +2 -0
- package/dist/invocation/types.js +1 -0
- package/dist/package/index.d.ts +1 -1
- package/dist/package/runtime.js +105 -15
- package/dist/platform/types.d.ts +48 -1
- package/dist/project/config-declarations.d.ts +61 -0
- package/dist/project/config-declarations.js +297 -0
- package/dist/project/index.d.ts +5 -2
- package/dist/project/index.js +5 -2
- package/dist/project/init.js +2 -2
- package/dist/project/loader.js +4 -0
- package/dist/project/manifest.js +10 -0
- package/dist/project/types.d.ts +30 -0
- package/dist/runtime/context.d.ts +6 -3
- package/dist/runtime/context.js +60 -13
- package/dist/runtime/env.js +15 -1
- package/dist/runtime/standalone-commands.d.ts +2 -2
- package/dist/runtime/standalone-commands.js +113 -47
- package/dist/runtime/standalone-help.d.ts +1 -0
- package/dist/runtime/standalone-help.js +15 -9
- package/dist/runtime/standalone.js +2 -2
- package/dist/server/policy.d.ts +24 -107
- package/dist/server/policy.js +134 -291
- package/dist/server/routes/actions.js +10 -8
- package/dist/server/routes/common.d.ts +5 -20
- package/dist/server/routes/common.js +22 -58
- package/dist/server/routes/config.js +65 -7
- package/dist/server/routes/doctor.d.ts +1 -0
- package/dist/server/routes/doctor.js +4 -3
- package/dist/server/routes/info.js +100 -13
- package/dist/server/routes/playbooks.js +1 -3
- package/dist/server/routes/runs.js +112 -18
- package/dist/server/server.d.ts +2 -1
- package/dist/server/server.js +38 -55
- package/dist/server/types.d.ts +6 -6
- package/dist/service/factory.js +1 -0
- package/dist/service/remote.js +28 -29
- package/dist/service/types.d.ts +34 -5
- package/dist/service/types.js +8 -0
- package/dist/storage/mask.d.ts +33 -6
- package/dist/storage/mask.js +298 -16
- package/dist/storage/sqlite-runs.d.ts +5 -17
- package/dist/storage/sqlite-runs.js +127 -5
- package/dist/storage/sqlite.d.ts +5 -17
- package/dist/storage/sqlite.js +2 -2
- package/dist/storage/types.d.ts +31 -16
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/server/policy.d.ts
CHANGED
|
@@ -3,144 +3,61 @@ import { ActionDockError } from "../errors.js";
|
|
|
3
3
|
import type { McpEndpointHandler } from "./mcp-endpoint.js";
|
|
4
4
|
import type { EffectiveServerPolicy, ServerOptions, ServerViewOptions } from "./types.js";
|
|
5
5
|
export { extractBearerToken } from "./security.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
type PolicyAction = {
|
|
7
|
+
actionId?: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
packageId?: string;
|
|
10
|
+
} | string;
|
|
11
|
+
/** 校验两类白名单授权;省略或空数组不增加授权,包列表中的独立 * 显式授予全部权限。 */
|
|
12
|
+
export declare function assertServerPolicyValid(item: {
|
|
13
|
+
packageAllowlist?: unknown;
|
|
14
|
+
actionAllowlist?: unknown;
|
|
15
|
+
}, scope?: string): void;
|
|
16
|
+
/** 是否显式授予全部包与动作;不替代独立的管理能力门禁。 */
|
|
17
|
+
export declare function isFullAccessGranted(policy?: EffectiveServerPolicy | null): boolean;
|
|
9
18
|
export declare class ActionForbiddenError extends ActionDockError {
|
|
10
19
|
status: number;
|
|
11
20
|
statusCode: number;
|
|
12
21
|
constructor(actionRef: string);
|
|
13
22
|
}
|
|
14
|
-
/**
|
|
15
|
-
* 包未列入白名单拒绝访问异常。
|
|
16
|
-
*/
|
|
17
23
|
export declare class PackageNotAllowedError extends ActionDockError {
|
|
18
24
|
status: number;
|
|
19
25
|
statusCode: number;
|
|
20
26
|
constructor(packageId: string);
|
|
21
27
|
}
|
|
22
|
-
/**
|
|
23
|
-
* 归一化后的单个服务视图结构。
|
|
24
|
-
*/
|
|
25
28
|
export interface NormalizedServerView {
|
|
26
|
-
/** 视图唯一标识名称 */
|
|
27
29
|
name: string;
|
|
28
|
-
/** 该视图生效的统一安全策略 */
|
|
29
30
|
policy: EffectiveServerPolicy;
|
|
30
|
-
/** 该视图是否启用 MCP 端点 */
|
|
31
31
|
enableMcp: boolean;
|
|
32
|
-
/** 原始配置选项 */
|
|
33
32
|
rawOptions: ServerViewOptions;
|
|
34
|
-
/** 为该视图独立装配的 MCP 端点处理器 */
|
|
35
33
|
mcpEndpointHandler?: McpEndpointHandler;
|
|
36
34
|
}
|
|
37
|
-
/**
|
|
38
|
-
* 根据客户端携带的 Token 智能匹配已注册的视图。
|
|
39
|
-
* 使用 safeEqual 进行恒定时间比较以防范时序攻击。
|
|
40
|
-
*
|
|
41
|
-
* @param token 客户端提供的 Bearer Token
|
|
42
|
-
* @param views 可遍历的视图集合
|
|
43
|
-
* @returns 匹配成功的视图对象,未命中返回 undefined
|
|
44
|
-
*/
|
|
35
|
+
/** 遍历全部视图并以常数时间比较令牌,避免提前退出泄露匹配位置。 */
|
|
45
36
|
export declare function matchViewByToken(token: string, views: Iterable<NormalizedServerView>): NormalizedServerView | undefined;
|
|
46
|
-
/**
|
|
47
|
-
* 归一化解析服务端配置的所有视图集合。
|
|
48
|
-
* 保证默认视图与显式自定义视图具有统一的数据结构。
|
|
49
|
-
*
|
|
50
|
-
* @param options 服务端配置选项
|
|
51
|
-
* @returns 默认视图与全部视图 Map 映射
|
|
52
|
-
*/
|
|
37
|
+
/** 全局选项仅配置默认视图;命名视图独立授权,缺少授权的视图默认拒绝全部。 */
|
|
53
38
|
export declare function normalizeServerViews(options: ServerOptions): {
|
|
54
39
|
defaultView: NormalizedServerView;
|
|
55
40
|
views: Map<string, NormalizedServerView>;
|
|
56
41
|
};
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
*
|
|
63
|
-
* @param action 目标动作对象或引用字符串
|
|
64
|
-
* @param allowlist 允许动作白名单列表
|
|
65
|
-
* @returns 是否允许访问
|
|
66
|
-
*/
|
|
67
|
-
export declare function isActionAllowed(action: {
|
|
68
|
-
actionId?: string;
|
|
69
|
-
id?: string;
|
|
70
|
-
packageId?: string;
|
|
71
|
-
} | string | null | undefined, allowlist?: string[]): boolean;
|
|
72
|
-
/**
|
|
73
|
-
* 根据生效策略统一判定目标动作是否允许访问。
|
|
74
|
-
* 同时综合校验策略中的 packageAllowlist 与 actionAllowlist 约束。
|
|
75
|
-
*
|
|
76
|
-
* @param action 目标动作对象或引用字符串
|
|
77
|
-
* @param policy 当前生效的服务端策略
|
|
78
|
-
* @returns 是否允许访问
|
|
79
|
-
*/
|
|
80
|
-
export declare function isActionAllowedByPolicy(action: {
|
|
81
|
-
actionId?: string;
|
|
82
|
-
id?: string;
|
|
83
|
-
packageId?: string;
|
|
84
|
-
} | string | null | undefined, policy?: EffectiveServerPolicy): boolean;
|
|
85
|
-
/**
|
|
86
|
-
* 根据生效策略统一判定目标包标识是否允许访问。
|
|
87
|
-
*
|
|
88
|
-
* @param packageId 目标包 ID
|
|
89
|
-
* @param policy 当前生效的服务端策略
|
|
90
|
-
* @returns 是否允许访问
|
|
91
|
-
*/
|
|
42
|
+
/** 动作列表仅按全限定引用匹配;省略或空列表不会授予任何动作。 */
|
|
43
|
+
export declare function isActionAllowed(action: PolicyAction | null | undefined, allowlist?: string[]): boolean;
|
|
44
|
+
/** 整包授权与全限定单动作授权取并集;仅缺少策略对象的内部调用不执行过滤。 */
|
|
45
|
+
export declare function isActionAllowedByPolicy(action: PolicyAction | null | undefined, policy?: EffectiveServerPolicy): boolean;
|
|
46
|
+
/** 整包能力授权,不从某个动作的授权推导配置、状态、规程或诊断权限。 */
|
|
92
47
|
export declare function isPackageAllowedByPolicy(packageId?: string | null, policy?: EffectiveServerPolicy): boolean;
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
* @param policy 当前生效的服务端策略
|
|
97
|
-
* @returns 是否允许管理操作
|
|
98
|
-
*/
|
|
48
|
+
/** 单动作授权的包可发现必要身份信息,但不因此获得整包能力。 */
|
|
49
|
+
export declare function isPackageVisibleByPolicy(packageId?: string | null, policy?: EffectiveServerPolicy): boolean;
|
|
50
|
+
export declare function requiresMinimalPackageProjection(packageId?: string | null, policy?: EffectiveServerPolicy): boolean;
|
|
99
51
|
export declare function isManagementAllowedByPolicy(policy?: EffectiveServerPolicy): boolean;
|
|
100
|
-
/**
|
|
101
|
-
* 根据生效策略对动作列表进行统一过滤。
|
|
102
|
-
*
|
|
103
|
-
* @param actions 待过滤的动作列表
|
|
104
|
-
* @param policy 当前生效的服务端策略
|
|
105
|
-
* @returns 过滤后允许访问的动作列表
|
|
106
|
-
*/
|
|
107
52
|
export declare function filterActionsByPolicy<T extends {
|
|
108
53
|
id: string;
|
|
109
54
|
packageId?: string;
|
|
110
55
|
actionId?: string;
|
|
111
56
|
}>(actions: T[], policy?: EffectiveServerPolicy): T[];
|
|
112
|
-
/**
|
|
113
|
-
* 根据生效策略对包列表进行统一过滤。
|
|
114
|
-
*
|
|
115
|
-
* @param packages 待过滤的包列表
|
|
116
|
-
* @param policy 当前生效的服务端策略
|
|
117
|
-
* @returns 过滤后允许访问的包列表
|
|
118
|
-
*/
|
|
119
57
|
export declare function filterPackagesByPolicy<T extends {
|
|
120
58
|
id: string;
|
|
121
59
|
}>(packages: T[], policy?: EffectiveServerPolicy): T[];
|
|
122
|
-
|
|
123
|
-
* 校验目标动作是否属于允许白名单,不符合则抛出 403 异常。
|
|
124
|
-
*
|
|
125
|
-
* @param action 目标动作对象或引用
|
|
126
|
-
* @param policy 当前生效的服务端策略
|
|
127
|
-
*/
|
|
128
|
-
export declare function assertActionAllowedByPolicy(action: {
|
|
129
|
-
actionId?: string;
|
|
130
|
-
id?: string;
|
|
131
|
-
packageId?: string;
|
|
132
|
-
} | string, policy?: EffectiveServerPolicy): void;
|
|
133
|
-
/**
|
|
134
|
-
* 校验目标包是否属于允许白名单,不符合则抛出 403 异常。
|
|
135
|
-
*
|
|
136
|
-
* @param packageId 目标包 ID
|
|
137
|
-
* @param policy 当前生效的服务端策略
|
|
138
|
-
*/
|
|
60
|
+
export declare function assertActionAllowedByPolicy(action: PolicyAction, policy?: EffectiveServerPolicy): void;
|
|
139
61
|
export declare function assertPackageAllowedByPolicy(packageId: string, policy?: EffectiveServerPolicy): void;
|
|
140
|
-
/**
|
|
141
|
-
* 构造受信任的服务端执行来源快照。
|
|
142
|
-
*
|
|
143
|
-
* 仅挑选 channel、viewName(默认 "default")与 authenticated(视图配置非空有效令牌)。
|
|
144
|
-
* 严禁暴露令牌原文、片段或摘要。
|
|
145
|
-
*/
|
|
62
|
+
/** 只记录受信任的执行来源,不能包含令牌原文、片段或摘要。 */
|
|
146
63
|
export declare function createServerRunOrigin(policy: Pick<EffectiveServerPolicy, "viewName" | "token">, channel: RunOrigin["channel"]): RunOrigin;
|
package/dist/server/policy.js
CHANGED
|
@@ -1,11 +1,48 @@
|
|
|
1
1
|
import { snapshotRunOrigin } from "../runtime/run-origin.js";
|
|
2
2
|
import { parseActionRef } from "../catalog/resolve-action.js";
|
|
3
|
-
import { ActionDockError, ACTION_FORBIDDEN, PACKAGE_NOT_ALLOWED } from "../errors.js";
|
|
3
|
+
import { ActionDockError, ACTION_FORBIDDEN, INVALID_ARGUMENT, PACKAGE_NOT_ALLOWED } from "../errors.js";
|
|
4
|
+
import { assertValidPackageId } from "../utils/index.js";
|
|
4
5
|
import { safeEqual } from "./security.js";
|
|
5
6
|
export { extractBearerToken } from "./security.js";
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/** 校验两类白名单授权;省略或空数组不增加授权,包列表中的独立 * 显式授予全部权限。 */
|
|
8
|
+
export function assertServerPolicyValid(item, scope = "server options") {
|
|
9
|
+
for (const field of ["packageAllowlist", "actionAllowlist"]) {
|
|
10
|
+
const list = item[field];
|
|
11
|
+
if (list === undefined)
|
|
12
|
+
continue;
|
|
13
|
+
if (!Array.isArray(list)) {
|
|
14
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid ${scope}.${field}: expected an array`);
|
|
15
|
+
}
|
|
16
|
+
for (const entry of list) {
|
|
17
|
+
if (typeof entry !== "string" || !entry.trim()) {
|
|
18
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid ${scope}.${field}: entries must be non-empty strings`);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
if (field === "packageAllowlist") {
|
|
22
|
+
if (entry !== "*")
|
|
23
|
+
assertValidPackageId(entry);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
if (entry.includes("*") || entry.includes("?")) {
|
|
27
|
+
throw new Error("wildcards are not supported in action references");
|
|
28
|
+
}
|
|
29
|
+
const ref = parseActionRef(entry);
|
|
30
|
+
if (!ref.packageId) {
|
|
31
|
+
throw new Error("action references must be fully qualified packageId/actionId references");
|
|
32
|
+
}
|
|
33
|
+
assertValidPackageId(ref.packageId);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid ${scope}.${field}: ${error instanceof Error ? error.message : String(error)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** 是否显式授予全部包与动作;不替代独立的管理能力门禁。 */
|
|
43
|
+
export function isFullAccessGranted(policy) {
|
|
44
|
+
return policy?.packageAllowlist?.includes("*") === true;
|
|
45
|
+
}
|
|
9
46
|
export class ActionForbiddenError extends ActionDockError {
|
|
10
47
|
status = 403;
|
|
11
48
|
statusCode = 403;
|
|
@@ -15,9 +52,6 @@ export class ActionForbiddenError extends ActionDockError {
|
|
|
15
52
|
Object.setPrototypeOf(this, ActionForbiddenError.prototype);
|
|
16
53
|
}
|
|
17
54
|
}
|
|
18
|
-
/**
|
|
19
|
-
* 包未列入白名单拒绝访问异常。
|
|
20
|
-
*/
|
|
21
55
|
export class PackageNotAllowedError extends ActionDockError {
|
|
22
56
|
status = 403;
|
|
23
57
|
statusCode = 403;
|
|
@@ -27,41 +61,43 @@ export class PackageNotAllowedError extends ActionDockError {
|
|
|
27
61
|
Object.setPrototypeOf(this, PackageNotAllowedError.prototype);
|
|
28
62
|
}
|
|
29
63
|
}
|
|
30
|
-
/**
|
|
31
|
-
* 根据客户端携带的 Token 智能匹配已注册的视图。
|
|
32
|
-
* 使用 safeEqual 进行恒定时间比较以防范时序攻击。
|
|
33
|
-
*
|
|
34
|
-
* @param token 客户端提供的 Bearer Token
|
|
35
|
-
* @param views 可遍历的视图集合
|
|
36
|
-
* @returns 匹配成功的视图对象,未命中返回 undefined
|
|
37
|
-
*/
|
|
64
|
+
/** 遍历全部视图并以常数时间比较令牌,避免提前退出泄露匹配位置。 */
|
|
38
65
|
export function matchViewByToken(token, views) {
|
|
39
66
|
const trimmed = token.trim();
|
|
40
|
-
if (!trimmed)
|
|
67
|
+
if (!trimmed)
|
|
41
68
|
return undefined;
|
|
42
|
-
|
|
43
|
-
let matched = undefined;
|
|
69
|
+
let matched;
|
|
44
70
|
for (const view of views) {
|
|
45
71
|
const candidateToken = view.policy.token?.trim();
|
|
46
72
|
if (candidateToken) {
|
|
47
73
|
const isMatch = safeEqual(trimmed, candidateToken);
|
|
48
|
-
if (isMatch && !matched)
|
|
74
|
+
if (isMatch && !matched)
|
|
49
75
|
matched = view;
|
|
50
|
-
}
|
|
51
76
|
}
|
|
52
77
|
}
|
|
53
78
|
return matched;
|
|
54
79
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
function normalizeView(item, name) {
|
|
81
|
+
assertServerPolicyValid(item, `view '${name}'`);
|
|
82
|
+
return {
|
|
83
|
+
name,
|
|
84
|
+
policy: {
|
|
85
|
+
viewName: name,
|
|
86
|
+
token: item.token,
|
|
87
|
+
packageAllowlist: item.packageAllowlist?.slice(),
|
|
88
|
+
actionAllowlist: item.actionAllowlist?.map((entry) => {
|
|
89
|
+
const ref = parseActionRef(entry);
|
|
90
|
+
return `${ref.packageId}/${ref.actionId}`;
|
|
91
|
+
}),
|
|
92
|
+
enableManagement: item.enableManagement,
|
|
93
|
+
},
|
|
94
|
+
enableMcp: item.enableMcp !== false,
|
|
95
|
+
rawOptions: item,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** 全局选项仅配置默认视图;命名视图独立授权,缺少授权的视图默认拒绝全部。 */
|
|
62
99
|
export function normalizeServerViews(options) {
|
|
63
|
-
|
|
64
|
-
// 1. 初始化基于全局配置的默认视图
|
|
100
|
+
assertServerPolicyValid(options);
|
|
65
101
|
const defaultRawOptions = {
|
|
66
102
|
name: "default",
|
|
67
103
|
token: options.token,
|
|
@@ -72,307 +108,114 @@ export function normalizeServerViews(options) {
|
|
|
72
108
|
mcpHandler: options.mcpHandler,
|
|
73
109
|
};
|
|
74
110
|
const customViews = new Map();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (name === "default") {
|
|
87
|
-
Object.assign(defaultRawOptions, item);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
customViews.set(name, {
|
|
91
|
-
name,
|
|
92
|
-
policy: {
|
|
93
|
-
viewName: name,
|
|
94
|
-
token: item.token,
|
|
95
|
-
packageAllowlist: item.packageAllowlist,
|
|
96
|
-
actionAllowlist: item.actionAllowlist,
|
|
97
|
-
enableManagement: item.enableManagement,
|
|
98
|
-
},
|
|
99
|
-
enableMcp: item.enableMcp !== false,
|
|
100
|
-
rawOptions: item,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
111
|
+
const entries = Array.isArray(options.views)
|
|
112
|
+
? options.views.map((item, index) => [`view_${index}`, item])
|
|
113
|
+
: Object.entries(options.views || {});
|
|
114
|
+
for (const [key, item] of entries) {
|
|
115
|
+
if (!item || typeof item !== "object")
|
|
116
|
+
continue;
|
|
117
|
+
const name = item.name?.trim() || key.trim();
|
|
118
|
+
if (!name || name === "." || name === "..")
|
|
119
|
+
continue;
|
|
120
|
+
if (name === "default") {
|
|
121
|
+
Object.assign(defaultRawOptions, item);
|
|
104
122
|
}
|
|
105
|
-
else
|
|
106
|
-
|
|
107
|
-
if (!item || typeof item !== "object")
|
|
108
|
-
continue;
|
|
109
|
-
const name = item.name?.trim() || key.trim();
|
|
110
|
-
if (!name || name === "." || name === "..") {
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (name === "default") {
|
|
114
|
-
Object.assign(defaultRawOptions, item);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
customViews.set(name, {
|
|
118
|
-
name,
|
|
119
|
-
policy: {
|
|
120
|
-
viewName: name,
|
|
121
|
-
token: item.token,
|
|
122
|
-
packageAllowlist: item.packageAllowlist,
|
|
123
|
-
actionAllowlist: item.actionAllowlist,
|
|
124
|
-
enableManagement: item.enableManagement,
|
|
125
|
-
},
|
|
126
|
-
enableMcp: item.enableMcp !== false,
|
|
127
|
-
rawOptions: item,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
123
|
+
else {
|
|
124
|
+
customViews.set(name, normalizeView(item, name));
|
|
131
125
|
}
|
|
132
126
|
}
|
|
133
|
-
const defaultView =
|
|
134
|
-
|
|
135
|
-
policy: {
|
|
136
|
-
viewName: "default",
|
|
137
|
-
token: defaultRawOptions.token,
|
|
138
|
-
packageAllowlist: defaultRawOptions.packageAllowlist,
|
|
139
|
-
actionAllowlist: defaultRawOptions.actionAllowlist,
|
|
140
|
-
enableManagement: defaultRawOptions.enableManagement,
|
|
141
|
-
},
|
|
142
|
-
enableMcp: defaultRawOptions.enableMcp !== false,
|
|
143
|
-
rawOptions: defaultRawOptions,
|
|
144
|
-
};
|
|
145
|
-
// 默认视图置于首位,随后装载自定义视图
|
|
146
|
-
views.set("default", defaultView);
|
|
147
|
-
for (const [k, v] of customViews) {
|
|
148
|
-
views.set(k, v);
|
|
149
|
-
}
|
|
127
|
+
const defaultView = normalizeView(defaultRawOptions, "default");
|
|
128
|
+
const views = new Map([["default", defaultView], ...customViews]);
|
|
150
129
|
return { defaultView, views };
|
|
151
130
|
}
|
|
152
|
-
/**
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
* - 白名单项若不含 '/'(如 act),则只需 actionId 匹配(不限包)。
|
|
157
|
-
*
|
|
158
|
-
* @param action 目标动作对象或引用字符串
|
|
159
|
-
* @param allowlist 允许动作白名单列表
|
|
160
|
-
* @returns 是否允许访问
|
|
161
|
-
*/
|
|
162
|
-
export function isActionAllowed(action, allowlist) {
|
|
163
|
-
if (!allowlist || !Array.isArray(allowlist) || allowlist.length === 0) {
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
if (!action) {
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
let target;
|
|
131
|
+
/** 目标引用格式错误或包身份不一致时返回未命中,不能猜测其所属包。 */
|
|
132
|
+
function parsePolicyAction(action) {
|
|
133
|
+
if (!action)
|
|
134
|
+
return undefined;
|
|
170
135
|
try {
|
|
171
|
-
if (typeof action === "string")
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
target = parseActionRef(rawId);
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
target = parseActionRef({
|
|
184
|
-
packageId: action.packageId,
|
|
185
|
-
actionId: rawId,
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
target = parseActionRef(rawId);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
136
|
+
if (typeof action === "string")
|
|
137
|
+
return parseActionRef(action);
|
|
138
|
+
const rawId = action.actionId || action.id;
|
|
139
|
+
if (!rawId)
|
|
140
|
+
return undefined;
|
|
141
|
+
const ref = parseActionRef(rawId);
|
|
142
|
+
if (action.packageId && ref.packageId && action.packageId !== ref.packageId)
|
|
143
|
+
return undefined;
|
|
144
|
+
return { packageId: action.packageId || ref.packageId, actionId: ref.actionId };
|
|
193
145
|
}
|
|
194
146
|
catch {
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
for (const item of allowlist) {
|
|
198
|
-
if (typeof item !== "string")
|
|
199
|
-
continue;
|
|
200
|
-
const trimmed = item.trim();
|
|
201
|
-
if (!trimmed)
|
|
202
|
-
continue;
|
|
203
|
-
try {
|
|
204
|
-
const rule = parseActionRef(trimmed);
|
|
205
|
-
if (rule.packageId) {
|
|
206
|
-
if (rule.packageId === target.packageId && rule.actionId === target.actionId) {
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
if (rule.actionId === target.actionId) {
|
|
212
|
-
return true;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
catch {
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
147
|
+
return undefined;
|
|
219
148
|
}
|
|
220
|
-
return false;
|
|
221
149
|
}
|
|
222
|
-
/**
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
150
|
+
/** 动作列表仅按全限定引用匹配;省略或空列表不会授予任何动作。 */
|
|
151
|
+
export function isActionAllowed(action, allowlist) {
|
|
152
|
+
const target = parsePolicyAction(action);
|
|
153
|
+
if (!target?.packageId)
|
|
154
|
+
return false;
|
|
155
|
+
return (allowlist || []).some((entry) => {
|
|
156
|
+
const rule = parsePolicyAction(entry);
|
|
157
|
+
return Boolean(rule?.packageId && rule.packageId === target.packageId && rule.actionId === target.actionId);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/** 整包授权与全限定单动作授权取并集;仅缺少策略对象的内部调用不执行过滤。 */
|
|
230
161
|
export function isActionAllowedByPolicy(action, policy) {
|
|
231
|
-
if (!policy)
|
|
162
|
+
if (!policy)
|
|
232
163
|
return true;
|
|
233
|
-
|
|
234
|
-
if (!
|
|
164
|
+
const target = parsePolicyAction(action);
|
|
165
|
+
if (!target)
|
|
235
166
|
return false;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
if (typeof action === "string") {
|
|
241
|
-
const parsed = parseActionRef(action);
|
|
242
|
-
pkgId = parsed.packageId;
|
|
243
|
-
actId = parsed.actionId;
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
pkgId = action.packageId;
|
|
247
|
-
const rawId = action.actionId || action.id;
|
|
248
|
-
if (!rawId)
|
|
249
|
-
return false;
|
|
250
|
-
if (rawId.includes("/")) {
|
|
251
|
-
const parsed = parseActionRef(rawId);
|
|
252
|
-
pkgId = parsed.packageId || pkgId;
|
|
253
|
-
actId = parsed.actionId;
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
actId = rawId;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
catch {
|
|
167
|
+
if (isFullAccessGranted(policy))
|
|
168
|
+
return true;
|
|
169
|
+
if (!target.packageId)
|
|
261
170
|
return false;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (policy.packageAllowlist && policy.packageAllowlist.length > 0) {
|
|
265
|
-
if (pkgId) {
|
|
266
|
-
if (!policy.packageAllowlist.includes(pkgId)) {
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
// 未指明所属包时,仅当其动作标识明确列入 actionAllowlist 时才允许放行
|
|
272
|
-
if (!policy.actionAllowlist ||
|
|
273
|
-
policy.actionAllowlist.length === 0 ||
|
|
274
|
-
!isActionAllowed({ actionId: actId }, policy.actionAllowlist)) {
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
// 2. 若配置了 actionAllowlist,校验动作白名单
|
|
280
|
-
if (policy.actionAllowlist && policy.actionAllowlist.length > 0) {
|
|
281
|
-
if (!isActionAllowed({ packageId: pkgId, actionId: actId }, policy.actionAllowlist)) {
|
|
282
|
-
return false;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return true;
|
|
171
|
+
return Boolean(policy.packageAllowlist?.includes(target.packageId) ||
|
|
172
|
+
isActionAllowed(target, policy.actionAllowlist));
|
|
286
173
|
}
|
|
287
|
-
/**
|
|
288
|
-
* 根据生效策略统一判定目标包标识是否允许访问。
|
|
289
|
-
*
|
|
290
|
-
* @param packageId 目标包 ID
|
|
291
|
-
* @param policy 当前生效的服务端策略
|
|
292
|
-
* @returns 是否允许访问
|
|
293
|
-
*/
|
|
174
|
+
/** 整包能力授权,不从某个动作的授权推导配置、状态、规程或诊断权限。 */
|
|
294
175
|
export function isPackageAllowedByPolicy(packageId, policy) {
|
|
295
|
-
if (!policy
|
|
176
|
+
if (!policy || isFullAccessGranted(policy))
|
|
296
177
|
return true;
|
|
297
|
-
|
|
298
|
-
|
|
178
|
+
return Boolean(packageId && policy.packageAllowlist?.includes(packageId));
|
|
179
|
+
}
|
|
180
|
+
/** 单动作授权的包可发现必要身份信息,但不因此获得整包能力。 */
|
|
181
|
+
export function isPackageVisibleByPolicy(packageId, policy) {
|
|
182
|
+
if (isPackageAllowedByPolicy(packageId, policy))
|
|
183
|
+
return true;
|
|
184
|
+
if (!packageId)
|
|
299
185
|
return false;
|
|
300
|
-
|
|
301
|
-
|
|
186
|
+
return (policy?.actionAllowlist || []).some((entry) => parsePolicyAction(entry)?.packageId === packageId);
|
|
187
|
+
}
|
|
188
|
+
export function requiresMinimalPackageProjection(packageId, policy) {
|
|
189
|
+
return isPackageVisibleByPolicy(packageId, policy) && !isPackageAllowedByPolicy(packageId, policy);
|
|
302
190
|
}
|
|
303
|
-
/**
|
|
304
|
-
* 根据生效策略统一判定配置与状态管理路由是否开启。
|
|
305
|
-
*
|
|
306
|
-
* @param policy 当前生效的服务端策略
|
|
307
|
-
* @returns 是否允许管理操作
|
|
308
|
-
*/
|
|
309
191
|
export function isManagementAllowedByPolicy(policy) {
|
|
310
192
|
return policy?.enableManagement === true;
|
|
311
193
|
}
|
|
312
|
-
/**
|
|
313
|
-
* 根据生效策略对动作列表进行统一过滤。
|
|
314
|
-
*
|
|
315
|
-
* @param actions 待过滤的动作列表
|
|
316
|
-
* @param policy 当前生效的服务端策略
|
|
317
|
-
* @returns 过滤后允许访问的动作列表
|
|
318
|
-
*/
|
|
319
194
|
export function filterActionsByPolicy(actions, policy) {
|
|
320
|
-
if (!policy)
|
|
321
|
-
return actions;
|
|
322
|
-
}
|
|
323
|
-
const hasPkg = policy.packageAllowlist && policy.packageAllowlist.length > 0;
|
|
324
|
-
const hasAct = policy.actionAllowlist && policy.actionAllowlist.length > 0;
|
|
325
|
-
if (!hasPkg && !hasAct) {
|
|
195
|
+
if (!policy)
|
|
326
196
|
return actions;
|
|
327
|
-
|
|
328
|
-
return actions.filter((a) => isActionAllowedByPolicy({ packageId: a.packageId, actionId: a.actionId || a.id }, policy));
|
|
197
|
+
return actions.filter((action) => isActionAllowedByPolicy(action, policy));
|
|
329
198
|
}
|
|
330
|
-
/**
|
|
331
|
-
* 根据生效策略对包列表进行统一过滤。
|
|
332
|
-
*
|
|
333
|
-
* @param packages 待过滤的包列表
|
|
334
|
-
* @param policy 当前生效的服务端策略
|
|
335
|
-
* @returns 过滤后允许访问的包列表
|
|
336
|
-
*/
|
|
337
199
|
export function filterPackagesByPolicy(packages, policy) {
|
|
338
|
-
if (!policy
|
|
200
|
+
if (!policy)
|
|
339
201
|
return packages;
|
|
340
|
-
|
|
341
|
-
return packages.filter((p) => p.id && policy.packageAllowlist.includes(p.id));
|
|
202
|
+
return packages.filter((pkg) => isPackageVisibleByPolicy(pkg.id, policy));
|
|
342
203
|
}
|
|
343
|
-
/**
|
|
344
|
-
* 校验目标动作是否属于允许白名单,不符合则抛出 403 异常。
|
|
345
|
-
*
|
|
346
|
-
* @param action 目标动作对象或引用
|
|
347
|
-
* @param policy 当前生效的服务端策略
|
|
348
|
-
*/
|
|
349
204
|
export function assertActionAllowedByPolicy(action, policy) {
|
|
350
205
|
if (!isActionAllowedByPolicy(action, policy)) {
|
|
351
|
-
const
|
|
206
|
+
const ref = typeof action === "string"
|
|
352
207
|
? action
|
|
353
208
|
: action.packageId
|
|
354
209
|
? `${action.packageId}/${action.actionId || action.id}`
|
|
355
210
|
: action.actionId || action.id || "unknown";
|
|
356
|
-
throw new ActionForbiddenError(
|
|
211
|
+
throw new ActionForbiddenError(ref);
|
|
357
212
|
}
|
|
358
213
|
}
|
|
359
|
-
/**
|
|
360
|
-
* 校验目标包是否属于允许白名单,不符合则抛出 403 异常。
|
|
361
|
-
*
|
|
362
|
-
* @param packageId 目标包 ID
|
|
363
|
-
* @param policy 当前生效的服务端策略
|
|
364
|
-
*/
|
|
365
214
|
export function assertPackageAllowedByPolicy(packageId, policy) {
|
|
366
|
-
if (!isPackageAllowedByPolicy(packageId, policy))
|
|
215
|
+
if (!isPackageAllowedByPolicy(packageId, policy))
|
|
367
216
|
throw new PackageNotAllowedError(packageId);
|
|
368
|
-
}
|
|
369
217
|
}
|
|
370
|
-
/**
|
|
371
|
-
* 构造受信任的服务端执行来源快照。
|
|
372
|
-
*
|
|
373
|
-
* 仅挑选 channel、viewName(默认 "default")与 authenticated(视图配置非空有效令牌)。
|
|
374
|
-
* 严禁暴露令牌原文、片段或摘要。
|
|
375
|
-
*/
|
|
218
|
+
/** 只记录受信任的执行来源,不能包含令牌原文、片段或摘要。 */
|
|
376
219
|
export function createServerRunOrigin(policy, channel) {
|
|
377
220
|
return snapshotRunOrigin({
|
|
378
221
|
channel,
|