@dsh-enhanced/lark-channel 0.1.3
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/LICENSE +21 -0
- package/README.md +342 -0
- package/bin/dsh-lark-setup.js +8 -0
- package/bin/dsh-supervised-growth-setup.js +8 -0
- package/cordis.patch.yml +21 -0
- package/lib/adapter.d.ts +135 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +965 -0
- package/lib/adapter.js.map +1 -0
- package/lib/approval.d.ts +26 -0
- package/lib/approval.d.ts.map +1 -0
- package/lib/approval.js +104 -0
- package/lib/approval.js.map +1 -0
- package/lib/config.d.ts +21 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +56 -0
- package/lib/config.js.map +1 -0
- package/lib/group-thread.d.ts +11 -0
- package/lib/group-thread.d.ts.map +1 -0
- package/lib/group-thread.js +22 -0
- package/lib/group-thread.js.map +1 -0
- package/lib/index.d.ts +27 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +30 -0
- package/lib/index.js.map +1 -0
- package/lib/launchd.d.ts +44 -0
- package/lib/launchd.d.ts.map +1 -0
- package/lib/launchd.js +205 -0
- package/lib/launchd.js.map +1 -0
- package/lib/model-picker.d.ts +24 -0
- package/lib/model-picker.d.ts.map +1 -0
- package/lib/model-picker.js +95 -0
- package/lib/model-picker.js.map +1 -0
- package/lib/normalize.d.ts +12 -0
- package/lib/normalize.d.ts.map +1 -0
- package/lib/normalize.js +77 -0
- package/lib/normalize.js.map +1 -0
- package/lib/permission-picker.d.ts +29 -0
- package/lib/permission-picker.d.ts.map +1 -0
- package/lib/permission-picker.js +145 -0
- package/lib/permission-picker.js.map +1 -0
- package/lib/progress.d.ts +15 -0
- package/lib/progress.d.ts.map +1 -0
- package/lib/progress.js +137 -0
- package/lib/progress.js.map +1 -0
- package/lib/resident.d.ts +13 -0
- package/lib/resident.d.ts.map +1 -0
- package/lib/resident.js +41 -0
- package/lib/resident.js.map +1 -0
- package/lib/sdk.d.ts +131 -0
- package/lib/sdk.d.ts.map +1 -0
- package/lib/sdk.js +945 -0
- package/lib/sdk.js.map +1 -0
- package/lib/service.d.ts +30 -0
- package/lib/service.d.ts.map +1 -0
- package/lib/service.js +157 -0
- package/lib/service.js.map +1 -0
- package/lib/setup-materialization.d.ts +15 -0
- package/lib/setup-materialization.d.ts.map +1 -0
- package/lib/setup-materialization.js +221 -0
- package/lib/setup-materialization.js.map +1 -0
- package/lib/setup-profile.d.ts +23 -0
- package/lib/setup-profile.d.ts.map +1 -0
- package/lib/setup-profile.js +1126 -0
- package/lib/setup-profile.js.map +1 -0
- package/lib/setup.d.ts +206 -0
- package/lib/setup.d.ts.map +1 -0
- package/lib/setup.js +2143 -0
- package/lib/setup.js.map +1 -0
- package/lib/supervised-growth-profile.d.ts +74 -0
- package/lib/supervised-growth-profile.d.ts.map +1 -0
- package/lib/supervised-growth-profile.js +485 -0
- package/lib/supervised-growth-profile.js.map +1 -0
- package/lib/supervised-growth-setup.d.ts +85 -0
- package/lib/supervised-growth-setup.d.ts.map +1 -0
- package/lib/supervised-growth-setup.js +371 -0
- package/lib/supervised-growth-setup.js.map +1 -0
- package/lib/systemd.d.ts +39 -0
- package/lib/systemd.d.ts.map +1 -0
- package/lib/systemd.js +142 -0
- package/lib/systemd.js.map +1 -0
- package/lib/tool-approval.d.ts +22 -0
- package/lib/tool-approval.d.ts.map +1 -0
- package/lib/tool-approval.js +107 -0
- package/lib/tool-approval.js.map +1 -0
- package/lib/types.d.ts +158 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +11 -0
- package/lib/types.js.map +1 -0
- package/lib/version.d.ts +2 -0
- package/lib/version.d.ts.map +1 -0
- package/lib/version.js +2 -0
- package/lib/version.js.map +1 -0
- package/lib/windows-task.d.ts +39 -0
- package/lib/windows-task.d.ts.map +1 -0
- package/lib/windows-task.js +117 -0
- package/lib/windows-task.js.map +1 -0
- package/lib/ws-card-callback.d.ts +7 -0
- package/lib/ws-card-callback.d.ts.map +1 -0
- package/lib/ws-card-callback.js +51 -0
- package/lib/ws-card-callback.js.map +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,1126 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { isAbsolute, join, win32 } from 'node:path';
|
|
3
|
+
import { externalPrincipalId } from '@dsh-enhanced/assistant-delivery';
|
|
4
|
+
import { isMap, isScalar, isSeq, parseDocument, } from 'yaml';
|
|
5
|
+
const setupKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
6
|
+
const providerKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._@/-]{0,255}$/u;
|
|
7
|
+
const presetIdPattern = /^[a-z0-9][a-z0-9-]*$/u;
|
|
8
|
+
// The grant is expressed as one wildcard allow rather than an enumerated
|
|
9
|
+
// allowlist. Tools are registered dynamically by whichever plugins and skills a
|
|
10
|
+
// deployment mounts, so any name list is stale the moment a new tool appears:
|
|
11
|
+
// the agent then reports a refusal the owner cannot fix from the chat, and every
|
|
12
|
+
// addition needs a setup release. Risk is already judged by behaviour in
|
|
13
|
+
// `assistant-policy`'s `tools/pre-execute` reviewer, which inspects arguments and
|
|
14
|
+
// still routes writes, network access and dangerous commands to approval, so this
|
|
15
|
+
// rule decides reachability, not privilege.
|
|
16
|
+
//
|
|
17
|
+
// Delivery keeps its rule bound to the exact preset, absolute workspace and
|
|
18
|
+
// `external` initiator. The local Web foreground grant deliberately accepts
|
|
19
|
+
// every preset/workspace because both are user-selectable and can change
|
|
20
|
+
// independently; it still applies only to human foreground Agent operations.
|
|
21
|
+
const TOOL_WILDCARD = '*';
|
|
22
|
+
const FOREGROUND_CAPABILITY_RULE_ID = `dsh-enhanced-foreground-capability-${TOOL_WILDCARD}`;
|
|
23
|
+
// No capability is denied at this setup layer. Explicit Policy denies retain
|
|
24
|
+
// precedence over the wildcard grants, while tool execution additionally stays
|
|
25
|
+
// behind the session sandbox/reviewer and parameter-risk gates.
|
|
26
|
+
//
|
|
27
|
+
// Populating this list again re-denies those ids: Policy resolves `deny` ahead
|
|
28
|
+
// of `allow` at any specificity, so entries here override the wildcard.
|
|
29
|
+
const deniedExternalTools = [];
|
|
30
|
+
// Per-tool ids emitted by earlier releases: first the enumerated allowlist, then
|
|
31
|
+
// the denylist that replaced it. Removal must still recognise both. A stale
|
|
32
|
+
// allow rule is one `--agent-tools disable` can no longer revoke, and a stale
|
|
33
|
+
// deny rule keeps overriding the wildcard and blocking the tool it names.
|
|
34
|
+
const retiredToolRuleIds = [
|
|
35
|
+
'bash', 'pwsh', 'read', 'glob', 'grep', 'skill',
|
|
36
|
+
'memory_search', 'wiki_search', 'wiki_read',
|
|
37
|
+
'memory_manage', 'wiki_upsert', 'wiki_lint',
|
|
38
|
+
'automation_create', 'automation_manage', 'automation_run',
|
|
39
|
+
'evolution_propose', 'knowledge_pin', 'knowledge_promote',
|
|
40
|
+
'heartbeat_scratch_update',
|
|
41
|
+
];
|
|
42
|
+
const managedToolRuleSuffixes = [
|
|
43
|
+
TOOL_WILDCARD, ...deniedExternalTools, ...retiredToolRuleIds,
|
|
44
|
+
];
|
|
45
|
+
const managedApprovalSources = [
|
|
46
|
+
'dsh-enhanced-personal-memory',
|
|
47
|
+
'dsh-enhanced-personal-wiki',
|
|
48
|
+
'dsh-enhanced-assistant-automations',
|
|
49
|
+
'dsh-enhanced-assistant-evolution',
|
|
50
|
+
];
|
|
51
|
+
const managedProfileRows = [
|
|
52
|
+
{ id: 'dsh-enhanced-lark-channel', packageName: '@dsh-enhanced/lark-channel' },
|
|
53
|
+
{ id: 'dsh-enhanced-credentials-keychain', packageName: '@dsh-enhanced/credentials-keychain' },
|
|
54
|
+
];
|
|
55
|
+
function requireSetupKey(value, field) {
|
|
56
|
+
const normalized = value.trim();
|
|
57
|
+
if (!setupKeyPattern.test(normalized))
|
|
58
|
+
throw new Error(`lark-channel setup: invalid ${field}`);
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
61
|
+
function requireAccount(value) {
|
|
62
|
+
const account = requireSetupKey(value, 'account');
|
|
63
|
+
if (account.includes('-legacy-')) {
|
|
64
|
+
throw new Error('lark-channel setup: invalid account (reserved "-legacy-" segment)');
|
|
65
|
+
}
|
|
66
|
+
return account;
|
|
67
|
+
}
|
|
68
|
+
function requireProviderKey(value, field) {
|
|
69
|
+
const normalized = value.trim();
|
|
70
|
+
if (!providerKeyPattern.test(normalized))
|
|
71
|
+
throw new Error(`lark-channel setup: invalid ${field}`);
|
|
72
|
+
return normalized;
|
|
73
|
+
}
|
|
74
|
+
function asMap(node, label) {
|
|
75
|
+
if (!isMap(node))
|
|
76
|
+
throw new Error(`lark-channel setup: ${label} must be a YAML mapping`);
|
|
77
|
+
return node;
|
|
78
|
+
}
|
|
79
|
+
function asSequence(node, label) {
|
|
80
|
+
if (!isSeq(node))
|
|
81
|
+
throw new Error(`lark-channel setup: ${label} must be a YAML sequence`);
|
|
82
|
+
return node;
|
|
83
|
+
}
|
|
84
|
+
function findRow(rows, id) {
|
|
85
|
+
return rows.items.find(item => isMap(item) && item.get('id') === id);
|
|
86
|
+
}
|
|
87
|
+
function ensureRow(document, rows, id) {
|
|
88
|
+
const existing = findRow(rows, id);
|
|
89
|
+
if (existing !== undefined)
|
|
90
|
+
return existing;
|
|
91
|
+
const created = document.createNode({ id, config: {} });
|
|
92
|
+
if (!isMap(created))
|
|
93
|
+
throw new Error('lark-channel setup: failed to create profile row');
|
|
94
|
+
rows.add(created);
|
|
95
|
+
return created;
|
|
96
|
+
}
|
|
97
|
+
function assertManagedProfileRowIntegrity(rows) {
|
|
98
|
+
for (const expected of managedProfileRows) {
|
|
99
|
+
const matches = rows.items.filter(item => isMap(item) && item.get('id') === expected.id);
|
|
100
|
+
if (matches.length > 1) {
|
|
101
|
+
throw new Error(`lark-channel setup: managed profile row ${expected.id} is duplicated; refusing to change it`);
|
|
102
|
+
}
|
|
103
|
+
const row = matches[0];
|
|
104
|
+
if (row === undefined)
|
|
105
|
+
continue;
|
|
106
|
+
const name = row.get('name', true);
|
|
107
|
+
const disabled = row.get('disabled', true);
|
|
108
|
+
if (name !== undefined && (!isScalar(name) || name.value !== expected.packageName)) {
|
|
109
|
+
throw new Error(`lark-channel setup: managed profile row ${expected.id} is shadowed by an invalid package`);
|
|
110
|
+
}
|
|
111
|
+
if (disabled !== undefined && (!isScalar(disabled) || disabled.value !== false)) {
|
|
112
|
+
throw new Error(`lark-channel setup: managed profile row ${expected.id} is disabled or invalid`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function upsertById(document, values, value) {
|
|
117
|
+
const node = document.createNode(value);
|
|
118
|
+
const id = value.id;
|
|
119
|
+
const index = values.items.findIndex(item => isMap(item) && item.get('id') === id);
|
|
120
|
+
if (index === -1)
|
|
121
|
+
values.add(node);
|
|
122
|
+
else
|
|
123
|
+
values.items[index] = node;
|
|
124
|
+
}
|
|
125
|
+
function isLiteralAbsolutePath(value) {
|
|
126
|
+
return typeof value === 'string'
|
|
127
|
+
&& (isAbsolute(value) || win32.isAbsolute(value))
|
|
128
|
+
&& !value.includes('*')
|
|
129
|
+
&& !value.includes('\0');
|
|
130
|
+
}
|
|
131
|
+
function sequenceEquals(node, expected) {
|
|
132
|
+
if (!isSeq(node) || node.items.length !== expected.length)
|
|
133
|
+
return false;
|
|
134
|
+
return node.items.every((item, index) => isScalar(item) && item.value === expected[index]);
|
|
135
|
+
}
|
|
136
|
+
function sequenceIncludes(node, expected) {
|
|
137
|
+
return isSeq(node) && node.items.some(item => isScalar(item) && item.value === expected);
|
|
138
|
+
}
|
|
139
|
+
function managedReplyIdentities(rules, account) {
|
|
140
|
+
const identities = new Map();
|
|
141
|
+
for (const item of rules.items) {
|
|
142
|
+
if (!isMap(item))
|
|
143
|
+
continue;
|
|
144
|
+
const id = item.get('id');
|
|
145
|
+
const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
|
|
146
|
+
if (managedId?.kind !== 'reply' || managedId.account !== account)
|
|
147
|
+
continue;
|
|
148
|
+
const identity = managedExternalRuleIdentity(item, managedId);
|
|
149
|
+
if (identity === undefined) {
|
|
150
|
+
throw new Error(`lark-channel setup: managed reply rule ${String(id)} is invalid`);
|
|
151
|
+
}
|
|
152
|
+
identities.set(`${identity.preset}\0${identity.workspace}`, identity);
|
|
153
|
+
}
|
|
154
|
+
return [...identities.values()];
|
|
155
|
+
}
|
|
156
|
+
function legacyIdentitySuffix(identity) {
|
|
157
|
+
const digest = createHash('sha256')
|
|
158
|
+
.update(identity.preset)
|
|
159
|
+
.update('\0')
|
|
160
|
+
.update(identity.workspace)
|
|
161
|
+
.digest('hex')
|
|
162
|
+
.slice(0, 16);
|
|
163
|
+
return `-legacy-${identity.preset}-${digest}`;
|
|
164
|
+
}
|
|
165
|
+
function configuredAgentIdentity(rows, dshHome) {
|
|
166
|
+
const delivery = findRow(rows, 'dsh-enhanced-assistant-delivery');
|
|
167
|
+
if (delivery === undefined) {
|
|
168
|
+
throw new Error('lark-channel setup: dsh-enhanced-assistant-delivery profile row is required');
|
|
169
|
+
}
|
|
170
|
+
const config = asMap(delivery.get('config', true), 'assistant-delivery config');
|
|
171
|
+
const preset = config.get('defaultAgentPreset');
|
|
172
|
+
if (typeof preset !== 'string' || !presetIdPattern.test(preset)) {
|
|
173
|
+
throw new Error('lark-channel setup: assistant-delivery defaultAgentPreset is invalid');
|
|
174
|
+
}
|
|
175
|
+
const configuredWorkspace = config.get('defaultWorkspace');
|
|
176
|
+
const defaultExpression = /^dshHomePath\((['"])assistant-workspace\1\)$/u;
|
|
177
|
+
const defaultWorkspace = !isAbsolute(dshHome) && win32.isAbsolute(dshHome)
|
|
178
|
+
? win32.join(dshHome, 'assistant-workspace')
|
|
179
|
+
: join(dshHome, 'assistant-workspace');
|
|
180
|
+
const workspace = typeof configuredWorkspace === 'string' && defaultExpression.test(configuredWorkspace)
|
|
181
|
+
? defaultWorkspace
|
|
182
|
+
: configuredWorkspace;
|
|
183
|
+
if (!isLiteralAbsolutePath(workspace)) {
|
|
184
|
+
throw new Error('lark-channel setup: assistant-delivery defaultWorkspace is invalid');
|
|
185
|
+
}
|
|
186
|
+
return { preset, workspace };
|
|
187
|
+
}
|
|
188
|
+
function upsertExternalReplyRule(document, rules, input) {
|
|
189
|
+
const suffix = input.legacy ? legacyIdentitySuffix(input.identity) : '';
|
|
190
|
+
upsertById(document, rules, {
|
|
191
|
+
id: `lark-owner-reply-${input.account}${suffix}`,
|
|
192
|
+
effect: 'allow',
|
|
193
|
+
subject: {
|
|
194
|
+
kind: 'agent',
|
|
195
|
+
id: input.identity.preset,
|
|
196
|
+
workspace: input.identity.workspace,
|
|
197
|
+
principal: input.principal,
|
|
198
|
+
},
|
|
199
|
+
actions: ['reply'],
|
|
200
|
+
resource: { kind: 'message', id: '*' },
|
|
201
|
+
context: { initiators: ['external'] },
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function upsertExternalToolRules(document, rules, input) {
|
|
205
|
+
const suffix = input.legacy ? legacyIdentitySuffix(input.identity) : '';
|
|
206
|
+
const subject = {
|
|
207
|
+
kind: 'agent',
|
|
208
|
+
id: input.identity.preset,
|
|
209
|
+
workspace: input.identity.workspace,
|
|
210
|
+
principal: input.principal,
|
|
211
|
+
};
|
|
212
|
+
// Tool reachability alone is insufficient for plugin tools whose service
|
|
213
|
+
// performs a second Policy check (memory.search, wiki.read, automation
|
|
214
|
+
// propose, and future domains). Keep the Agent identity exact, but allow its
|
|
215
|
+
// full external capability plane; tools/pre-execute and every explicit deny
|
|
216
|
+
// still run before/inside the operation.
|
|
217
|
+
upsertById(document, rules, {
|
|
218
|
+
id: `lark-owner-capability-${TOOL_WILDCARD}-${input.account}${suffix}`,
|
|
219
|
+
effect: 'allow',
|
|
220
|
+
subject,
|
|
221
|
+
actions: [TOOL_WILDCARD],
|
|
222
|
+
resource: { kind: TOOL_WILDCARD, id: TOOL_WILDCARD },
|
|
223
|
+
context: { initiators: ['external'] },
|
|
224
|
+
});
|
|
225
|
+
// One reachability grant for every tool the mounted preset exposes.
|
|
226
|
+
upsertById(document, rules, {
|
|
227
|
+
id: `lark-owner-tool-${TOOL_WILDCARD}-${input.account}${suffix}`,
|
|
228
|
+
effect: 'allow',
|
|
229
|
+
subject,
|
|
230
|
+
actions: ['execute'],
|
|
231
|
+
resource: { kind: 'tool', id: TOOL_WILDCARD },
|
|
232
|
+
context: { initiators: ['external'] },
|
|
233
|
+
});
|
|
234
|
+
// Keep any explicitly configured denials after the broad reachability grant
|
|
235
|
+
// so deny precedence remains obvious in the serialized policy.
|
|
236
|
+
for (const tool of deniedExternalTools) {
|
|
237
|
+
upsertById(document, rules, {
|
|
238
|
+
id: `lark-owner-tool-${tool}-${input.account}${suffix}`,
|
|
239
|
+
effect: 'deny',
|
|
240
|
+
subject,
|
|
241
|
+
actions: ['execute'],
|
|
242
|
+
resource: { kind: 'tool', id: tool },
|
|
243
|
+
context: { initiators: ['external'] },
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function upsertForegroundCapabilityRule(document, rules) {
|
|
248
|
+
upsertById(document, rules, {
|
|
249
|
+
id: FOREGROUND_CAPABILITY_RULE_ID,
|
|
250
|
+
effect: 'allow',
|
|
251
|
+
subject: { kind: 'agent', id: TOOL_WILDCARD, workspace: TOOL_WILDCARD },
|
|
252
|
+
actions: [TOOL_WILDCARD],
|
|
253
|
+
resource: { kind: TOOL_WILDCARD, id: TOOL_WILDCARD },
|
|
254
|
+
context: { initiators: ['foreground'] },
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function isManagedForegroundRuleId(id) {
|
|
258
|
+
return id === FOREGROUND_CAPABILITY_RULE_ID
|
|
259
|
+
// Migrate account-scoped ids written before the foreground grant was
|
|
260
|
+
// separated from Lark onboarding. They all represented this same local
|
|
261
|
+
// capability and are safe to collapse into one idempotent rule.
|
|
262
|
+
|| id.startsWith('lark-foreground-capability-')
|
|
263
|
+
|| id.startsWith('lark-foreground-tool-');
|
|
264
|
+
}
|
|
265
|
+
function isReservedForegroundRuleId(id) {
|
|
266
|
+
return id.startsWith('dsh-enhanced-foreground-capability-')
|
|
267
|
+
|| id.startsWith('lark-foreground-capability-')
|
|
268
|
+
|| id.startsWith('lark-foreground-tool-');
|
|
269
|
+
}
|
|
270
|
+
function isUnrestrictedForegroundAllow(item) {
|
|
271
|
+
const subject = item.get('subject', true);
|
|
272
|
+
const resource = item.get('resource', true);
|
|
273
|
+
const context = item.get('context', true);
|
|
274
|
+
return item.get('effect') === 'allow'
|
|
275
|
+
&& isMap(subject)
|
|
276
|
+
&& subject.get('kind') === 'agent'
|
|
277
|
+
&& subject.get('id') === TOOL_WILDCARD
|
|
278
|
+
&& subject.get('workspace') === TOOL_WILDCARD
|
|
279
|
+
// Rebuilding a principal-restricted foreground rule without the principal
|
|
280
|
+
// would widen it, even when its remaining wildcard shape is unchanged.
|
|
281
|
+
&& subject.get('principal') === undefined
|
|
282
|
+
&& sequenceEquals(item.get('actions', true), [TOOL_WILDCARD])
|
|
283
|
+
&& isMap(resource)
|
|
284
|
+
&& resource.get('kind') === TOOL_WILDCARD
|
|
285
|
+
&& resource.get('id') === TOOL_WILDCARD
|
|
286
|
+
&& isMap(context)
|
|
287
|
+
&& sequenceEquals(context.get('initiators', true), ['foreground']);
|
|
288
|
+
}
|
|
289
|
+
function isSafeRestrictiveForegroundRule(item) {
|
|
290
|
+
if (item.get('effect') === 'deny')
|
|
291
|
+
return true;
|
|
292
|
+
if (item.get('effect') !== 'allow')
|
|
293
|
+
return false;
|
|
294
|
+
const subject = item.get('subject', true);
|
|
295
|
+
const context = item.get('context', true);
|
|
296
|
+
if (!isMap(subject) || subject.get('kind') !== 'agent' || !isMap(context)
|
|
297
|
+
|| !sequenceEquals(context.get('initiators', true), ['foreground'])) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
const principal = subject.get('principal');
|
|
301
|
+
// A literal principal narrows the local rule. A glob is still setup-wide
|
|
302
|
+
// authority and is not accepted as a hand-edited restriction.
|
|
303
|
+
return principal === undefined
|
|
304
|
+
|| (typeof principal === 'string' && !principal.includes(TOOL_WILDCARD));
|
|
305
|
+
}
|
|
306
|
+
function assertManagedForegroundRuleIntegrity(rules) {
|
|
307
|
+
const seenIds = new Set();
|
|
308
|
+
for (const item of rules.items) {
|
|
309
|
+
if (!isMap(item))
|
|
310
|
+
continue;
|
|
311
|
+
const id = item.get('id');
|
|
312
|
+
if (typeof id !== 'string' || !isReservedForegroundRuleId(id))
|
|
313
|
+
continue;
|
|
314
|
+
if (seenIds.has(id)) {
|
|
315
|
+
throw new Error(`lark-channel setup: duplicate managed foreground rule ${id}; refusing to change it`);
|
|
316
|
+
}
|
|
317
|
+
seenIds.add(id);
|
|
318
|
+
if (isManagedForegroundRuleId(id)
|
|
319
|
+
&& (isUnrestrictedForegroundAllow(item) || isSafeRestrictiveForegroundRule(item)))
|
|
320
|
+
continue;
|
|
321
|
+
throw new Error(`lark-channel setup: managed foreground rule ${id} is unsafe or malformed; refusing to change Agent policy`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function hasManagedForegroundRules(rules) {
|
|
325
|
+
const managed = rules.items.filter(item => isMap(item)
|
|
326
|
+
&& typeof item.get('id') === 'string'
|
|
327
|
+
&& isManagedForegroundRuleId(item.get('id')));
|
|
328
|
+
return managed.length > 0 && managed.every(isUnrestrictedForegroundAllow);
|
|
329
|
+
}
|
|
330
|
+
function removeManagedForegroundRules(rules, includeDenies = false) {
|
|
331
|
+
for (let index = rules.items.length - 1; index >= 0; index -= 1) {
|
|
332
|
+
const item = rules.items[index];
|
|
333
|
+
if (!isMap(item))
|
|
334
|
+
continue;
|
|
335
|
+
const id = item.get('id');
|
|
336
|
+
if (typeof id !== 'string' || !isManagedForegroundRuleId(id))
|
|
337
|
+
continue;
|
|
338
|
+
if (includeDenies && item.get('effect') === 'deny') {
|
|
339
|
+
const context = item.get('context', true);
|
|
340
|
+
if (!isMap(context)
|
|
341
|
+
|| !sequenceEquals(context.get('initiators', true), ['foreground'])) {
|
|
342
|
+
throw new Error(`lark-channel setup: managed foreground deny ${id} may protect external requests; `
|
|
343
|
+
+ 'refusing to remove it while enabling Agent policy');
|
|
344
|
+
}
|
|
345
|
+
rules.items.splice(index, 1);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
// Disable may safely remove either the canonical allow or a local narrowed
|
|
349
|
+
// allow, but preserves denies because deleting one could widen another
|
|
350
|
+
// rule. Explicit enable deliberately retires those denies before rebuilding
|
|
351
|
+
// the requested full-control grant.
|
|
352
|
+
if (isUnrestrictedForegroundAllow(item)
|
|
353
|
+
|| (item.get('effect') === 'allow' && isSafeRestrictiveForegroundRule(item))) {
|
|
354
|
+
rules.items.splice(index, 1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function parseManagedAccountTail(tail) {
|
|
359
|
+
const legacyIndex = tail.indexOf('-legacy-');
|
|
360
|
+
const account = legacyIndex === -1 ? tail : tail.slice(0, legacyIndex);
|
|
361
|
+
if (!setupKeyPattern.test(account) || account.includes('-legacy-'))
|
|
362
|
+
return undefined;
|
|
363
|
+
if (legacyIndex === -1)
|
|
364
|
+
return { account };
|
|
365
|
+
const legacySuffix = tail.slice(legacyIndex);
|
|
366
|
+
if (legacySuffix.length <= '-legacy-'.length)
|
|
367
|
+
return undefined;
|
|
368
|
+
return { account, legacySuffix };
|
|
369
|
+
}
|
|
370
|
+
function parseManagedRuleId(id) {
|
|
371
|
+
const replyPrefix = 'lark-owner-reply-';
|
|
372
|
+
if (id.startsWith(replyPrefix)) {
|
|
373
|
+
const tail = parseManagedAccountTail(id.slice(replyPrefix.length));
|
|
374
|
+
return tail === undefined ? undefined : { kind: 'reply', ...tail };
|
|
375
|
+
}
|
|
376
|
+
const capabilityPrefix = `lark-owner-capability-${TOOL_WILDCARD}-`;
|
|
377
|
+
if (id.startsWith(capabilityPrefix)) {
|
|
378
|
+
const tail = parseManagedAccountTail(id.slice(capabilityPrefix.length));
|
|
379
|
+
return tail === undefined ? undefined : { kind: 'capability', ...tail };
|
|
380
|
+
}
|
|
381
|
+
for (const tool of managedToolRuleSuffixes) {
|
|
382
|
+
const toolPrefix = `lark-owner-tool-${tool}-`;
|
|
383
|
+
if (!id.startsWith(toolPrefix))
|
|
384
|
+
continue;
|
|
385
|
+
const tail = parseManagedAccountTail(id.slice(toolPrefix.length));
|
|
386
|
+
return tail === undefined ? undefined : { kind: 'tool', tool, ...tail };
|
|
387
|
+
}
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
390
|
+
function isReservedManagedExternalRuleId(id) {
|
|
391
|
+
return id.startsWith('lark-owner-reply-')
|
|
392
|
+
|| id.startsWith('lark-owner-capability-')
|
|
393
|
+
|| id.startsWith('lark-owner-tool-');
|
|
394
|
+
}
|
|
395
|
+
function managedExternalRuleIdentity(item, managedId) {
|
|
396
|
+
const subject = item.get('subject', true);
|
|
397
|
+
const resource = item.get('resource', true);
|
|
398
|
+
const context = item.get('context', true);
|
|
399
|
+
if (!isMap(subject) || !isMap(resource) || !isMap(context)
|
|
400
|
+
|| subject.get('kind') !== 'agent'
|
|
401
|
+
|| typeof subject.get('id') !== 'string'
|
|
402
|
+
|| !presetIdPattern.test(subject.get('id'))
|
|
403
|
+
|| !isLiteralAbsolutePath(subject.get('workspace'))
|
|
404
|
+
|| !sequenceEquals(context.get('initiators', true), ['external']))
|
|
405
|
+
return undefined;
|
|
406
|
+
const identity = {
|
|
407
|
+
preset: subject.get('id'),
|
|
408
|
+
workspace: subject.get('workspace'),
|
|
409
|
+
};
|
|
410
|
+
if (managedId.legacySuffix !== undefined
|
|
411
|
+
&& managedId.legacySuffix !== legacyIdentitySuffix(identity))
|
|
412
|
+
return undefined;
|
|
413
|
+
const principal = subject.get('principal');
|
|
414
|
+
if (principal !== undefined && !isCanonicalLarkPrincipalForAccount(principal, managedId.account)) {
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
if (managedId.kind === 'reply') {
|
|
418
|
+
return item.get('effect') === 'allow'
|
|
419
|
+
&& sequenceEquals(item.get('actions', true), ['reply'])
|
|
420
|
+
&& resource.get('kind') === 'message'
|
|
421
|
+
&& resource.get('id') === TOOL_WILDCARD
|
|
422
|
+
? identity
|
|
423
|
+
: undefined;
|
|
424
|
+
}
|
|
425
|
+
if (managedId.kind === 'tool') {
|
|
426
|
+
return (item.get('effect') === 'allow' || item.get('effect') === 'deny')
|
|
427
|
+
&& sequenceEquals(item.get('actions', true), ['execute'])
|
|
428
|
+
&& resource.get('kind') === 'tool'
|
|
429
|
+
// One retired release changed only the rule id while retaining the old
|
|
430
|
+
// wildcard resource. Treat that exact Agent/execute/tool shape as ours
|
|
431
|
+
// too, so upgrades can still revoke it.
|
|
432
|
+
&& (resource.get('id') === managedId.tool
|
|
433
|
+
|| (managedId.tool !== TOOL_WILDCARD && resource.get('id') === TOOL_WILDCARD))
|
|
434
|
+
? identity
|
|
435
|
+
: undefined;
|
|
436
|
+
}
|
|
437
|
+
return (item.get('effect') === 'allow' || item.get('effect') === 'deny')
|
|
438
|
+
&& sequenceEquals(item.get('actions', true), [TOOL_WILDCARD])
|
|
439
|
+
&& resource.get('kind') === TOOL_WILDCARD
|
|
440
|
+
&& resource.get('id') === TOOL_WILDCARD
|
|
441
|
+
? identity
|
|
442
|
+
: undefined;
|
|
443
|
+
}
|
|
444
|
+
function assertManagedExternalRuleIntegrity(rules) {
|
|
445
|
+
const seenIds = new Set();
|
|
446
|
+
for (const item of rules.items) {
|
|
447
|
+
if (!isMap(item))
|
|
448
|
+
continue;
|
|
449
|
+
const id = item.get('id');
|
|
450
|
+
// These ids are setup-reserved. Exact current and explicit legacy shapes
|
|
451
|
+
// are accepted by the strict recognizer above; every other mutation is
|
|
452
|
+
// ambiguous and fails closed, regardless of which privilege plane it was
|
|
453
|
+
// changed to. User policy must use a non-reserved id.
|
|
454
|
+
if (typeof id !== 'string' || !isReservedManagedExternalRuleId(id))
|
|
455
|
+
continue;
|
|
456
|
+
if (seenIds.has(id)) {
|
|
457
|
+
throw new Error(`lark-channel setup: duplicate managed Agent rule ${id}; refusing to change it`);
|
|
458
|
+
}
|
|
459
|
+
seenIds.add(id);
|
|
460
|
+
const managedId = parseManagedRuleId(id);
|
|
461
|
+
if (managedId !== undefined && managedExternalRuleIdentity(item, managedId) !== undefined)
|
|
462
|
+
continue;
|
|
463
|
+
throw new Error(`lark-channel setup: reserved managed Agent rule ${id} is malformed; refusing to change Agent policy`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function isCanonicalLarkPrincipalForAccount(value, account) {
|
|
467
|
+
if (typeof value !== 'string')
|
|
468
|
+
return false;
|
|
469
|
+
const parts = value.split('/');
|
|
470
|
+
return parts.length === 4
|
|
471
|
+
&& parts[0] === 'lark'
|
|
472
|
+
&& parts[1] === encodeURIComponent(account)
|
|
473
|
+
&& isCanonicalPrincipalComponent(parts[2], setupKeyPattern)
|
|
474
|
+
&& isCanonicalPrincipalComponent(parts[3], providerKeyPattern);
|
|
475
|
+
}
|
|
476
|
+
function isCanonicalPrincipalComponent(value, pattern) {
|
|
477
|
+
if (value === undefined || value === '' || value.includes(TOOL_WILDCARD))
|
|
478
|
+
return false;
|
|
479
|
+
try {
|
|
480
|
+
const decoded = decodeURIComponent(value);
|
|
481
|
+
return pattern.test(decoded) && encodeURIComponent(decoded) === value;
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function parseManagedFixedRuleId(id) {
|
|
488
|
+
const fixedPrefixes = [
|
|
489
|
+
{ prefix: 'lark-channel-credential-', kind: 'credential' },
|
|
490
|
+
{ prefix: 'lark-owner-ingress-', kind: 'ingress' },
|
|
491
|
+
];
|
|
492
|
+
for (const candidate of fixedPrefixes) {
|
|
493
|
+
if (!id.startsWith(candidate.prefix))
|
|
494
|
+
continue;
|
|
495
|
+
const account = id.slice(candidate.prefix.length);
|
|
496
|
+
if (!setupKeyPattern.test(account) || account.includes('-legacy-'))
|
|
497
|
+
return undefined;
|
|
498
|
+
return { kind: candidate.kind, account };
|
|
499
|
+
}
|
|
500
|
+
for (const sourceId of managedApprovalSources) {
|
|
501
|
+
const prefix = `lark-owner-approval-${sourceId}-`;
|
|
502
|
+
if (!id.startsWith(prefix))
|
|
503
|
+
continue;
|
|
504
|
+
const account = id.slice(prefix.length);
|
|
505
|
+
if (!setupKeyPattern.test(account) || account.includes('-legacy-'))
|
|
506
|
+
return undefined;
|
|
507
|
+
return { kind: 'approval', account, sourceId };
|
|
508
|
+
}
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
function isReservedManagedFixedRuleId(id) {
|
|
512
|
+
return id.startsWith('lark-channel-credential-')
|
|
513
|
+
|| id.startsWith('lark-owner-ingress-')
|
|
514
|
+
|| id.startsWith('lark-owner-approval-');
|
|
515
|
+
}
|
|
516
|
+
function managedFixedRuleMatches(item, managedId) {
|
|
517
|
+
const subject = item.get('subject', true);
|
|
518
|
+
const resource = item.get('resource', true);
|
|
519
|
+
const context = item.get('context', true);
|
|
520
|
+
if (item.get('effect') !== 'allow' || !isMap(subject) || !isMap(resource) || !isMap(context)) {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
if (managedId.kind === 'credential') {
|
|
524
|
+
return subject.get('kind') === 'background'
|
|
525
|
+
&& subject.get('id') === 'dsh-enhanced-lark-channel'
|
|
526
|
+
&& sequenceEquals(item.get('actions', true), ['credential.use'])
|
|
527
|
+
&& resource.get('kind') === 'credential'
|
|
528
|
+
&& resource.get('id') === `lark-app-secret-${managedId.account}`
|
|
529
|
+
&& sequenceEquals(context.get('initiators', true), ['background']);
|
|
530
|
+
}
|
|
531
|
+
if (managedId.kind === 'ingress') {
|
|
532
|
+
return subject.get('kind') === 'external'
|
|
533
|
+
&& isCanonicalLarkPrincipalForAccount(subject.get('id'), managedId.account)
|
|
534
|
+
&& sequenceEquals(item.get('actions', true), ['approval.decide', 'ingest'])
|
|
535
|
+
&& resource.get('kind') === 'message'
|
|
536
|
+
&& resource.get('id') === TOOL_WILDCARD
|
|
537
|
+
&& sequenceEquals(context.get('initiators', true), ['external']);
|
|
538
|
+
}
|
|
539
|
+
const principal = subject.get('principal');
|
|
540
|
+
return subject.get('kind') === 'background'
|
|
541
|
+
&& subject.get('id') === managedId.sourceId
|
|
542
|
+
&& isLiteralAbsolutePath(subject.get('workspace'))
|
|
543
|
+
// Principal-scoped approvals are current, while exact legacy approvals
|
|
544
|
+
// without a principal are still attributable to setup by the remaining
|
|
545
|
+
// source/action/resource/context tuple.
|
|
546
|
+
&& (principal === undefined || isCanonicalLarkPrincipalForAccount(principal, managedId.account))
|
|
547
|
+
&& sequenceEquals(item.get('actions', true), ['approval.send'])
|
|
548
|
+
&& resource.get('kind') === 'message'
|
|
549
|
+
&& resource.get('id') === TOOL_WILDCARD
|
|
550
|
+
&& sequenceEquals(context.get('initiators', true), ['background']);
|
|
551
|
+
}
|
|
552
|
+
function assertManagedFixedRuleIntegrity(rules, currentAccount) {
|
|
553
|
+
const seenIds = new Set();
|
|
554
|
+
for (const item of rules.items) {
|
|
555
|
+
if (!isMap(item))
|
|
556
|
+
continue;
|
|
557
|
+
const id = item.get('id');
|
|
558
|
+
const managedId = typeof id === 'string' ? parseManagedFixedRuleId(id) : undefined;
|
|
559
|
+
if (typeof id !== 'string' || !isReservedManagedFixedRuleId(id))
|
|
560
|
+
continue;
|
|
561
|
+
if (seenIds.has(id)) {
|
|
562
|
+
throw new Error(`lark-channel setup: duplicate managed fixed rule ${id}; refusing to change it`);
|
|
563
|
+
}
|
|
564
|
+
seenIds.add(id);
|
|
565
|
+
if (managedId !== undefined && managedFixedRuleMatches(item, managedId))
|
|
566
|
+
continue;
|
|
567
|
+
const scope = managedId === undefined
|
|
568
|
+
? 'reserved fixed'
|
|
569
|
+
: managedId.account === currentAccount ? 'fixed' : 'retired fixed';
|
|
570
|
+
throw new Error(`lark-channel setup: ${scope} rule ${id} is malformed/ambiguous; refusing to change it`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function sweepRetiredManagedFixedRules(rules, currentAccount) {
|
|
574
|
+
assertManagedFixedRuleIntegrity(rules, currentAccount);
|
|
575
|
+
for (let index = rules.items.length - 1; index >= 0; index -= 1) {
|
|
576
|
+
const item = rules.items[index];
|
|
577
|
+
if (!isMap(item))
|
|
578
|
+
continue;
|
|
579
|
+
const id = item.get('id');
|
|
580
|
+
const managedId = typeof id === 'string' ? parseManagedFixedRuleId(id) : undefined;
|
|
581
|
+
if (managedId !== undefined
|
|
582
|
+
&& managedId.account !== currentAccount
|
|
583
|
+
&& managedFixedRuleMatches(item, managedId))
|
|
584
|
+
rules.items.splice(index, 1);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
function parseManagedCredentialHandleId(id) {
|
|
588
|
+
const prefix = 'lark-app-secret-';
|
|
589
|
+
if (!id.startsWith(prefix))
|
|
590
|
+
return undefined;
|
|
591
|
+
const account = id.slice(prefix.length);
|
|
592
|
+
return setupKeyPattern.test(account) && !account.includes('-legacy-') ? account : undefined;
|
|
593
|
+
}
|
|
594
|
+
function managedCredentialHandleMatches(item) {
|
|
595
|
+
const provider = item.get('provider');
|
|
596
|
+
const commonShape = sequenceEquals(item.get('consumers', true), ['dsh-enhanced-lark-channel'])
|
|
597
|
+
&& sequenceEquals(item.get('purposes', true), ['connect'])
|
|
598
|
+
&& item.get('maxLeaseMs') === 86_400_000;
|
|
599
|
+
if (!commonShape)
|
|
600
|
+
return false;
|
|
601
|
+
if (provider === 'windows-dpapi')
|
|
602
|
+
return isLiteralAbsolutePath(item.get('path'));
|
|
603
|
+
return (provider === 'linux-secret-service' || provider === 'macos-keychain')
|
|
604
|
+
&& typeof item.get('service') === 'string'
|
|
605
|
+
&& typeof item.get('account') === 'string';
|
|
606
|
+
}
|
|
607
|
+
function sweepRetiredManagedCredentialHandles(rows, currentAccount) {
|
|
608
|
+
const credentialsRow = findRow(rows, 'dsh-enhanced-credentials-keychain');
|
|
609
|
+
if (credentialsRow === undefined)
|
|
610
|
+
return;
|
|
611
|
+
const credentials = asMap(credentialsRow.get('config', true), 'credentials-keychain config');
|
|
612
|
+
const handles = credentials.get('handles', true);
|
|
613
|
+
if (handles === undefined)
|
|
614
|
+
return;
|
|
615
|
+
const handleList = asSequence(handles, 'credentials-keychain handles');
|
|
616
|
+
let foundCurrent = false;
|
|
617
|
+
for (let index = handleList.items.length - 1; index >= 0; index -= 1) {
|
|
618
|
+
const item = handleList.items[index];
|
|
619
|
+
if (!isMap(item))
|
|
620
|
+
continue;
|
|
621
|
+
const id = item.get('id');
|
|
622
|
+
const managedAccount = typeof id === 'string' ? parseManagedCredentialHandleId(id) : undefined;
|
|
623
|
+
if (managedAccount === undefined)
|
|
624
|
+
continue;
|
|
625
|
+
if (managedAccount === currentAccount) {
|
|
626
|
+
if (foundCurrent) {
|
|
627
|
+
throw new Error(`lark-channel setup: current credential handle ${id} is duplicate; refusing to overwrite it`);
|
|
628
|
+
}
|
|
629
|
+
foundCurrent = true;
|
|
630
|
+
if (!managedCredentialHandleMatches(item)) {
|
|
631
|
+
throw new Error(`lark-channel setup: current credential handle ${id} is ambiguous; refusing to overwrite it`);
|
|
632
|
+
}
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (managedCredentialHandleMatches(item)) {
|
|
636
|
+
handleList.items.splice(index, 1);
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
if (sequenceIncludes(item.get('consumers', true), 'dsh-enhanced-lark-channel')) {
|
|
640
|
+
throw new Error(`lark-channel setup: retired credential handle ${id} is ambiguous; refusing to remove it`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function hasManagedExternalWildcardAllow(rules, account, ownerPrincipal) {
|
|
645
|
+
if (ownerPrincipal === undefined)
|
|
646
|
+
return false;
|
|
647
|
+
const wildcardKinds = new Map();
|
|
648
|
+
let foundCandidate = false;
|
|
649
|
+
for (const item of rules.items) {
|
|
650
|
+
if (!isMap(item))
|
|
651
|
+
continue;
|
|
652
|
+
const id = item.get('id');
|
|
653
|
+
const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
|
|
654
|
+
if (managedId === undefined
|
|
655
|
+
|| managedId.account !== account
|
|
656
|
+
|| managedId.kind === 'reply')
|
|
657
|
+
continue;
|
|
658
|
+
foundCandidate = true;
|
|
659
|
+
const identity = managedExternalRuleIdentity(item, managedId);
|
|
660
|
+
const subject = item.get('subject', true);
|
|
661
|
+
if (identity === undefined
|
|
662
|
+
|| item.get('effect') !== 'allow'
|
|
663
|
+
|| !isMap(subject)
|
|
664
|
+
|| (subject.get('principal') !== undefined && subject.get('principal') !== ownerPrincipal)) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
const isWildcard = managedId.kind === 'capability'
|
|
668
|
+
|| (managedId.kind === 'tool' && managedId.tool === TOOL_WILDCARD);
|
|
669
|
+
if (!isWildcard)
|
|
670
|
+
continue;
|
|
671
|
+
const identityKey = `${identity.preset}\0${identity.workspace}`;
|
|
672
|
+
const kinds = wildcardKinds.get(identityKey) ?? new Set();
|
|
673
|
+
if (kinds.has(managedId.kind))
|
|
674
|
+
return false;
|
|
675
|
+
kinds.add(managedId.kind);
|
|
676
|
+
wildcardKinds.set(identityKey, kinds);
|
|
677
|
+
}
|
|
678
|
+
if (!foundCandidate || wildcardKinds.size === 0)
|
|
679
|
+
return false;
|
|
680
|
+
// Earlier setup versions paired the wildcard allow with managed per-tool
|
|
681
|
+
// denies. Rebuilding only the allow for a new account would silently widen
|
|
682
|
+
// that historical policy, so preserve mode fails closed whenever any
|
|
683
|
+
// setup-managed rule for the source account is restrictive or malformed.
|
|
684
|
+
return [...wildcardKinds.values()].every(kinds => kinds.has('capability') && kinds.has('tool'));
|
|
685
|
+
}
|
|
686
|
+
function scopeManagedExternalAllowsToOwner(rules, account, ownerPrincipal) {
|
|
687
|
+
for (const item of rules.items) {
|
|
688
|
+
if (!isMap(item) || item.get('effect') !== 'allow')
|
|
689
|
+
continue;
|
|
690
|
+
const id = item.get('id');
|
|
691
|
+
const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
|
|
692
|
+
if (managedId === undefined || managedId.account !== account)
|
|
693
|
+
continue;
|
|
694
|
+
const subject = item.get('subject', true);
|
|
695
|
+
// Integrity validation runs first, so a setup-reserved rule reaching this
|
|
696
|
+
// branch always has the strict Agent subject shape.
|
|
697
|
+
if (!isMap(subject)) {
|
|
698
|
+
throw new Error(`lark-channel setup: managed Agent rule ${id} is malformed`);
|
|
699
|
+
}
|
|
700
|
+
const rulePrincipal = subject.get('principal');
|
|
701
|
+
if (rulePrincipal === undefined) {
|
|
702
|
+
// Earlier releases emitted external allows without a principal. Narrow
|
|
703
|
+
// them in place before preserve can retain the rule.
|
|
704
|
+
subject.set('principal', ownerPrincipal);
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (rulePrincipal !== ownerPrincipal) {
|
|
708
|
+
throw new Error(`lark-channel setup: managed Agent rule ${id} targets a different owner principal; `
|
|
709
|
+
+ 'refusing to preserve it');
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function hasManagedExternalRuleCandidate(rules, account) {
|
|
714
|
+
return rules.items.some(item => {
|
|
715
|
+
if (!isMap(item))
|
|
716
|
+
return false;
|
|
717
|
+
const id = item.get('id');
|
|
718
|
+
const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
|
|
719
|
+
return managedId !== undefined
|
|
720
|
+
&& managedId.kind !== 'reply'
|
|
721
|
+
&& managedId.account === account;
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
function hasManagedExternalAllowCandidate(rules, account) {
|
|
725
|
+
return rules.items.some(item => {
|
|
726
|
+
if (!isMap(item) || item.get('effect') !== 'allow')
|
|
727
|
+
return false;
|
|
728
|
+
const id = item.get('id');
|
|
729
|
+
const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
|
|
730
|
+
return managedId !== undefined
|
|
731
|
+
&& managedId.kind !== 'reply'
|
|
732
|
+
&& managedId.account === account;
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
function removeAllManagedExternalToolRules(rules, includeDenies = false) {
|
|
736
|
+
for (let index = rules.items.length - 1; index >= 0; index -= 1) {
|
|
737
|
+
const item = rules.items[index];
|
|
738
|
+
if (!isMap(item))
|
|
739
|
+
continue;
|
|
740
|
+
const id = item.get('id');
|
|
741
|
+
const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
|
|
742
|
+
if (managedId !== undefined
|
|
743
|
+
&& managedId.kind !== 'reply'
|
|
744
|
+
&& (includeDenies || item.get('effect') === 'allow')
|
|
745
|
+
&& managedExternalRuleIdentity(item, managedId) !== undefined)
|
|
746
|
+
rules.items.splice(index, 1);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
function removeManagedExternalToolRulesOutsideAccount(rules, account) {
|
|
750
|
+
for (let index = rules.items.length - 1; index >= 0; index -= 1) {
|
|
751
|
+
const item = rules.items[index];
|
|
752
|
+
if (!isMap(item))
|
|
753
|
+
continue;
|
|
754
|
+
const id = item.get('id');
|
|
755
|
+
const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
|
|
756
|
+
if (managedId !== undefined
|
|
757
|
+
&& managedId.kind !== 'reply'
|
|
758
|
+
&& managedId.account !== account
|
|
759
|
+
&& item.get('effect') === 'allow'
|
|
760
|
+
&& managedExternalRuleIdentity(item, managedId) !== undefined)
|
|
761
|
+
rules.items.splice(index, 1);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function removeAllManagedReplyRules(rules) {
|
|
765
|
+
for (let index = rules.items.length - 1; index >= 0; index -= 1) {
|
|
766
|
+
const item = rules.items[index];
|
|
767
|
+
if (!isMap(item))
|
|
768
|
+
continue;
|
|
769
|
+
const id = item.get('id');
|
|
770
|
+
const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
|
|
771
|
+
if (managedId?.kind === 'reply'
|
|
772
|
+
&& managedExternalRuleIdentity(item, managedId) !== undefined)
|
|
773
|
+
rules.items.splice(index, 1);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function removeAllManagedExternalAgentRules(rules, includeDenies = false) {
|
|
777
|
+
removeAllManagedExternalToolRules(rules, includeDenies);
|
|
778
|
+
removeAllManagedReplyRules(rules);
|
|
779
|
+
}
|
|
780
|
+
function upsertApprovalRules(document, rules, input) {
|
|
781
|
+
for (const sourceId of managedApprovalSources) {
|
|
782
|
+
upsertById(document, rules, {
|
|
783
|
+
id: `lark-owner-approval-${sourceId}-${input.account}`,
|
|
784
|
+
effect: 'allow',
|
|
785
|
+
subject: {
|
|
786
|
+
kind: 'background',
|
|
787
|
+
id: sourceId,
|
|
788
|
+
workspace: input.workspace,
|
|
789
|
+
principal: input.principal,
|
|
790
|
+
},
|
|
791
|
+
actions: ['approval.send'],
|
|
792
|
+
resource: { kind: 'message', id: '*' },
|
|
793
|
+
context: { initiators: ['background'] },
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function configuredOwnerPrincipal(rules, account) {
|
|
798
|
+
const rule = rules.items.find(item => isMap(item) && item.get('id') === `lark-owner-ingress-${account}`);
|
|
799
|
+
if (!isMap(rule) || rule.get('effect') !== 'allow')
|
|
800
|
+
return undefined;
|
|
801
|
+
const subject = rule.get('subject', true);
|
|
802
|
+
const resource = rule.get('resource', true);
|
|
803
|
+
const context = rule.get('context', true);
|
|
804
|
+
if (!isMap(subject) || !isMap(resource) || !isMap(context)
|
|
805
|
+
|| subject.get('kind') !== 'external'
|
|
806
|
+
|| !sequenceEquals(rule.get('actions', true), ['approval.decide', 'ingest'])
|
|
807
|
+
|| resource.get('kind') !== 'message'
|
|
808
|
+
|| resource.get('id') !== TOOL_WILDCARD
|
|
809
|
+
|| !sequenceEquals(context.get('initiators', true), ['external']))
|
|
810
|
+
return undefined;
|
|
811
|
+
const principal = subject.get('id');
|
|
812
|
+
return isCanonicalLarkPrincipalForAccount(principal, account) ? principal : undefined;
|
|
813
|
+
}
|
|
814
|
+
function setDefaults(map, defaults) {
|
|
815
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
816
|
+
if (!map.has(key))
|
|
817
|
+
map.set(key, value);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
export function refreshLarkAgentPolicyPatch(input) {
|
|
821
|
+
if (!isLiteralAbsolutePath(input.dshHome)) {
|
|
822
|
+
throw new Error('lark-channel setup: DSH home must be an absolute path');
|
|
823
|
+
}
|
|
824
|
+
if (input.agentTools !== 'disable' && input.agentTools !== 'enable') {
|
|
825
|
+
throw new Error('lark-channel setup: refresh agentTools mode must be enable or disable');
|
|
826
|
+
}
|
|
827
|
+
const document = parseDocument(input.profilePatch);
|
|
828
|
+
if (document.errors.length > 0) {
|
|
829
|
+
throw new Error(`lark-channel setup: profile patch is invalid YAML: ${document.errors[0]?.message}`);
|
|
830
|
+
}
|
|
831
|
+
const rows = asSequence(document.contents, 'profile patch');
|
|
832
|
+
assertManagedProfileRowIntegrity(rows);
|
|
833
|
+
const personalAssistant = findRow(rows, 'dsh-enhanced-personal-assistant');
|
|
834
|
+
if (personalAssistant === undefined) {
|
|
835
|
+
throw new Error('lark-channel setup: dsh-enhanced-personal-assistant profile override is required');
|
|
836
|
+
}
|
|
837
|
+
const personalConfig = asMap(personalAssistant.get('config', true), 'personal-assistant config');
|
|
838
|
+
const policy = asMap(personalConfig.get('assistantPolicy', true), 'assistantPolicy config');
|
|
839
|
+
const rules = asSequence(policy.get('rules', true), 'assistantPolicy rules');
|
|
840
|
+
// Exact-principal rules remain attributable even after their account is
|
|
841
|
+
// retired. Do not let explicit enable/disable report success while one of
|
|
842
|
+
// those executable rules survives because its context was hand-edited.
|
|
843
|
+
assertManagedExternalRuleIntegrity(rules);
|
|
844
|
+
assertManagedForegroundRuleIntegrity(rules);
|
|
845
|
+
// Foreground Web/direct reachability is a profile concern, not a channel
|
|
846
|
+
// binding concern. Apply it even when Lark is absent or deliberately skipped.
|
|
847
|
+
removeManagedForegroundRules(rules, input.agentTools === 'enable');
|
|
848
|
+
if (input.agentTools === 'enable')
|
|
849
|
+
upsertForegroundCapabilityRule(document, rules);
|
|
850
|
+
const lark = findRow(rows, 'dsh-enhanced-lark-channel');
|
|
851
|
+
if (lark === undefined) {
|
|
852
|
+
removeAllManagedExternalAgentRules(rules);
|
|
853
|
+
sweepRetiredManagedFixedRules(rules, undefined);
|
|
854
|
+
sweepRetiredManagedCredentialHandles(rows, undefined);
|
|
855
|
+
if (input.account !== undefined) {
|
|
856
|
+
throw new Error('lark-channel setup: no enabled Lark account exists to match the requested account');
|
|
857
|
+
}
|
|
858
|
+
return document.toString({ lineWidth: 0 });
|
|
859
|
+
}
|
|
860
|
+
const channel = asMap(lark.get('config', true), 'lark-channel config');
|
|
861
|
+
const configuredAccountValue = channel.get('account');
|
|
862
|
+
if (typeof configuredAccountValue !== 'string') {
|
|
863
|
+
throw new Error('lark-channel setup: existing Lark channel account is invalid');
|
|
864
|
+
}
|
|
865
|
+
const configuredAccount = requireAccount(configuredAccountValue);
|
|
866
|
+
assertManagedExternalRuleIntegrity(rules);
|
|
867
|
+
sweepRetiredManagedFixedRules(rules, configuredAccount);
|
|
868
|
+
sweepRetiredManagedCredentialHandles(rows, configuredAccount);
|
|
869
|
+
if (channel.get('enabled') !== true) {
|
|
870
|
+
// No disabled channel may leave an executable Agent rule behind, including
|
|
871
|
+
// reply grants and rules for retired account ids.
|
|
872
|
+
removeAllManagedExternalAgentRules(rules);
|
|
873
|
+
if (input.account !== undefined) {
|
|
874
|
+
throw new Error('lark-channel setup: no enabled Lark account exists to match the requested account');
|
|
875
|
+
}
|
|
876
|
+
return document.toString({ lineWidth: 0 });
|
|
877
|
+
}
|
|
878
|
+
if (input.account !== undefined && requireAccount(input.account) !== configuredAccount) {
|
|
879
|
+
throw new Error(`lark-channel setup: configured account is ${configuredAccount}; refusing a different account`);
|
|
880
|
+
}
|
|
881
|
+
const agent = configuredAgentIdentity(rows, input.dshHome);
|
|
882
|
+
const principal = configuredOwnerPrincipal(rules, configuredAccount);
|
|
883
|
+
if (input.agentTools === 'enable' && principal === undefined) {
|
|
884
|
+
throw new Error(`lark-channel setup: enabled account ${configuredAccount} has no canonical owner ingress; `
|
|
885
|
+
+ 'refusing to enable Agent policy');
|
|
886
|
+
}
|
|
887
|
+
const legacyAgents = managedReplyIdentities(rules, configuredAccount)
|
|
888
|
+
.filter(identity => identity.preset !== agent.preset || identity.workspace !== agent.workspace);
|
|
889
|
+
removeAllManagedExternalAgentRules(rules, input.agentTools === 'enable');
|
|
890
|
+
if (principal !== undefined) {
|
|
891
|
+
upsertExternalReplyRule(document, rules, { account: configuredAccount, identity: agent, principal });
|
|
892
|
+
for (const legacyAgent of legacyAgents) {
|
|
893
|
+
upsertExternalReplyRule(document, rules, {
|
|
894
|
+
account: configuredAccount,
|
|
895
|
+
identity: legacyAgent,
|
|
896
|
+
principal,
|
|
897
|
+
legacy: true,
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
if (input.agentTools === 'enable') {
|
|
901
|
+
upsertExternalToolRules(document, rules, {
|
|
902
|
+
account: configuredAccount,
|
|
903
|
+
identity: agent,
|
|
904
|
+
principal,
|
|
905
|
+
});
|
|
906
|
+
for (const legacyAgent of legacyAgents) {
|
|
907
|
+
upsertExternalToolRules(document, rules, {
|
|
908
|
+
account: configuredAccount,
|
|
909
|
+
identity: legacyAgent,
|
|
910
|
+
principal,
|
|
911
|
+
legacy: true,
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return document.toString({ lineWidth: 0 });
|
|
917
|
+
}
|
|
918
|
+
export function configureLarkProfilePatch(input) {
|
|
919
|
+
const appId = input.appId.trim();
|
|
920
|
+
if (!/^cli_[0-9a-fA-F]{16}$/u.test(appId))
|
|
921
|
+
throw new Error('lark-channel setup: invalid appId');
|
|
922
|
+
const account = requireAccount(input.account);
|
|
923
|
+
const tenant = requireSetupKey(input.tenant, 'tenant');
|
|
924
|
+
const ownerUserId = requireProviderKey(input.ownerUserId, 'ownerUserId');
|
|
925
|
+
const keychainService = requireProviderKey(input.keychainService, 'keychainService');
|
|
926
|
+
const keychainAccount = requireProviderKey(input.keychainAccount, 'keychainAccount');
|
|
927
|
+
const credentialProvider = input.credentialProvider ?? 'macos-keychain';
|
|
928
|
+
if (!['linux-secret-service', 'macos-keychain', 'windows-dpapi'].includes(credentialProvider)) {
|
|
929
|
+
throw new Error('lark-channel setup: invalid credentialProvider');
|
|
930
|
+
}
|
|
931
|
+
const agentToolsMode = input.agentTools ?? 'preserve';
|
|
932
|
+
if (!['disable', 'enable', 'preserve'].includes(agentToolsMode)) {
|
|
933
|
+
throw new Error('lark-channel setup: invalid agentTools mode');
|
|
934
|
+
}
|
|
935
|
+
const credentialHandle = `lark-app-secret-${account}`;
|
|
936
|
+
const document = parseDocument(input.profilePatch);
|
|
937
|
+
if (document.errors.length > 0) {
|
|
938
|
+
throw new Error(`lark-channel setup: profile patch is invalid YAML: ${document.errors[0]?.message}`);
|
|
939
|
+
}
|
|
940
|
+
const rows = asSequence(document.contents, 'profile patch');
|
|
941
|
+
assertManagedProfileRowIntegrity(rows);
|
|
942
|
+
const existingLarkRow = findRow(rows, 'dsh-enhanced-lark-channel');
|
|
943
|
+
let previousAccount;
|
|
944
|
+
if (existingLarkRow !== undefined) {
|
|
945
|
+
const existingChannel = asMap(existingLarkRow.get('config', true), 'lark-channel config');
|
|
946
|
+
const existingAccount = existingChannel.get('account');
|
|
947
|
+
if (existingAccount !== undefined) {
|
|
948
|
+
if (typeof existingAccount !== 'string') {
|
|
949
|
+
throw new Error('lark-channel setup: existing Lark channel account is invalid');
|
|
950
|
+
}
|
|
951
|
+
previousAccount = requireAccount(existingAccount);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
const personalAssistant = findRow(rows, 'dsh-enhanced-personal-assistant');
|
|
955
|
+
if (personalAssistant === undefined) {
|
|
956
|
+
throw new Error('lark-channel setup: dsh-enhanced-personal-assistant profile override is required');
|
|
957
|
+
}
|
|
958
|
+
const personalConfig = asMap(personalAssistant.get('config', true), 'personal-assistant config');
|
|
959
|
+
const policy = asMap(personalConfig.get('assistantPolicy', true), 'assistantPolicy config');
|
|
960
|
+
const rules = asSequence(policy.get('rules', true), 'assistantPolicy rules');
|
|
961
|
+
const grantSourceAccount = previousAccount ?? account;
|
|
962
|
+
const principalId = externalPrincipalId({ channel: 'lark', account, tenant, user: ownerUserId });
|
|
963
|
+
assertManagedExternalRuleIntegrity(rules);
|
|
964
|
+
assertManagedForegroundRuleIntegrity(rules);
|
|
965
|
+
assertManagedFixedRuleIntegrity(rules, account);
|
|
966
|
+
const sourceOwnerPrincipal = configuredOwnerPrincipal(rules, grantSourceAccount);
|
|
967
|
+
const preserveOwnerPrincipal = sourceOwnerPrincipal
|
|
968
|
+
?? (previousAccount === undefined ? principalId : undefined);
|
|
969
|
+
if (agentToolsMode === 'preserve'
|
|
970
|
+
&& previousAccount !== undefined
|
|
971
|
+
&& sourceOwnerPrincipal === undefined
|
|
972
|
+
&& hasManagedExternalAllowCandidate(rules, grantSourceAccount)) {
|
|
973
|
+
throw new Error(`lark-channel setup: enabled account ${grantSourceAccount} has no canonical owner ingress; `
|
|
974
|
+
+ 'refusing to preserve ambiguous Agent grants');
|
|
975
|
+
}
|
|
976
|
+
if (agentToolsMode === 'preserve' && preserveOwnerPrincipal !== undefined) {
|
|
977
|
+
scopeManagedExternalAllowsToOwner(rules, grantSourceAccount, preserveOwnerPrincipal);
|
|
978
|
+
}
|
|
979
|
+
const preservedExternalGrant = hasManagedExternalWildcardAllow(rules, grantSourceAccount, preserveOwnerPrincipal);
|
|
980
|
+
const preservedForegroundGrant = hasManagedForegroundRules(rules);
|
|
981
|
+
const ownerBindingChanged = (previousAccount !== undefined && previousAccount !== account)
|
|
982
|
+
|| (sourceOwnerPrincipal !== undefined && sourceOwnerPrincipal !== principalId);
|
|
983
|
+
if (agentToolsMode === 'preserve'
|
|
984
|
+
&& ownerBindingChanged
|
|
985
|
+
&& hasManagedExternalRuleCandidate(rules, grantSourceAccount)
|
|
986
|
+
&& !preservedExternalGrant) {
|
|
987
|
+
throw new Error('lark-channel setup: cannot preserve restrictive or ambiguous external Agent rules '
|
|
988
|
+
+ 'while changing the Lark owner binding; rerun with agentTools enable or disable');
|
|
989
|
+
}
|
|
990
|
+
const agent = configuredAgentIdentity(rows, input.dshHome);
|
|
991
|
+
const legacyAgents = previousAccount === undefined || previousAccount === account
|
|
992
|
+
? managedReplyIdentities(rules, account)
|
|
993
|
+
.filter(identity => identity.preset !== agent.preset || identity.workspace !== agent.workspace)
|
|
994
|
+
: [];
|
|
995
|
+
sweepRetiredManagedFixedRules(rules, account);
|
|
996
|
+
upsertById(document, rules, {
|
|
997
|
+
id: `lark-channel-credential-${account}`,
|
|
998
|
+
effect: 'allow',
|
|
999
|
+
subject: { kind: 'background', id: 'dsh-enhanced-lark-channel' },
|
|
1000
|
+
actions: ['credential.use'],
|
|
1001
|
+
resource: { kind: 'credential', id: credentialHandle },
|
|
1002
|
+
context: { initiators: ['background'] },
|
|
1003
|
+
});
|
|
1004
|
+
upsertApprovalRules(document, rules, { account, workspace: agent.workspace, principal: principalId });
|
|
1005
|
+
upsertById(document, rules, {
|
|
1006
|
+
id: `lark-owner-ingress-${account}`,
|
|
1007
|
+
effect: 'allow',
|
|
1008
|
+
subject: { kind: 'external', id: principalId },
|
|
1009
|
+
actions: ['approval.decide', 'ingest'],
|
|
1010
|
+
resource: { kind: 'message', id: '*' },
|
|
1011
|
+
context: { initiators: ['external'] },
|
|
1012
|
+
});
|
|
1013
|
+
// Rebuild setup-managed external rules from a single exact account owner.
|
|
1014
|
+
// This also sweeps rules left by retired accounts and migrates legacy rules
|
|
1015
|
+
// which predate principal-scoped Agent subjects.
|
|
1016
|
+
const retainCurrentExternalRules = agentToolsMode === 'preserve'
|
|
1017
|
+
&& !ownerBindingChanged
|
|
1018
|
+
&& !preservedExternalGrant;
|
|
1019
|
+
if (retainCurrentExternalRules)
|
|
1020
|
+
removeManagedExternalToolRulesOutsideAccount(rules, account);
|
|
1021
|
+
else
|
|
1022
|
+
removeAllManagedExternalToolRules(rules, agentToolsMode === 'enable');
|
|
1023
|
+
if (agentToolsMode === 'disable')
|
|
1024
|
+
removeManagedForegroundRules(rules);
|
|
1025
|
+
if (agentToolsMode === 'enable') {
|
|
1026
|
+
removeManagedForegroundRules(rules, true);
|
|
1027
|
+
upsertForegroundCapabilityRule(document, rules);
|
|
1028
|
+
upsertExternalToolRules(document, rules, { account, identity: agent, principal: principalId });
|
|
1029
|
+
for (const legacyAgent of legacyAgents) {
|
|
1030
|
+
upsertExternalToolRules(document, rules, {
|
|
1031
|
+
account,
|
|
1032
|
+
identity: legacyAgent,
|
|
1033
|
+
principal: principalId,
|
|
1034
|
+
legacy: true,
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (agentToolsMode === 'preserve') {
|
|
1039
|
+
if (preservedForegroundGrant) {
|
|
1040
|
+
removeManagedForegroundRules(rules);
|
|
1041
|
+
upsertForegroundCapabilityRule(document, rules);
|
|
1042
|
+
}
|
|
1043
|
+
if (preservedExternalGrant) {
|
|
1044
|
+
upsertExternalToolRules(document, rules, { account, identity: agent, principal: principalId });
|
|
1045
|
+
for (const legacyAgent of legacyAgents) {
|
|
1046
|
+
upsertExternalToolRules(document, rules, {
|
|
1047
|
+
account,
|
|
1048
|
+
identity: legacyAgent,
|
|
1049
|
+
principal: principalId,
|
|
1050
|
+
legacy: true,
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
removeAllManagedReplyRules(rules);
|
|
1056
|
+
upsertExternalReplyRule(document, rules, { account, identity: agent, principal: principalId });
|
|
1057
|
+
for (const legacyAgent of legacyAgents) {
|
|
1058
|
+
upsertExternalReplyRule(document, rules, {
|
|
1059
|
+
account,
|
|
1060
|
+
identity: legacyAgent,
|
|
1061
|
+
principal: principalId,
|
|
1062
|
+
legacy: true,
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
const credentialsRow = ensureRow(document, rows, 'dsh-enhanced-credentials-keychain');
|
|
1066
|
+
const credentials = asMap(credentialsRow.get('config', true), 'credentials-keychain config');
|
|
1067
|
+
setDefaults(credentials, {
|
|
1068
|
+
databasePath: join(input.dshHome, 'credentials-keychain/ledger.sqlite'),
|
|
1069
|
+
defaultLeaseMs: 300_000,
|
|
1070
|
+
maxSecretBytes: 65_536,
|
|
1071
|
+
providerTimeoutMs: 5_000,
|
|
1072
|
+
});
|
|
1073
|
+
let handles = credentials.get('handles', true);
|
|
1074
|
+
if (handles === undefined) {
|
|
1075
|
+
credentials.set('handles', document.createNode([]));
|
|
1076
|
+
handles = credentials.get('handles', true);
|
|
1077
|
+
}
|
|
1078
|
+
const handleList = asSequence(handles, 'credentials-keychain handles');
|
|
1079
|
+
sweepRetiredManagedCredentialHandles(rows, account);
|
|
1080
|
+
const commonHandle = {
|
|
1081
|
+
id: credentialHandle,
|
|
1082
|
+
consumers: ['dsh-enhanced-lark-channel'],
|
|
1083
|
+
purposes: ['connect'],
|
|
1084
|
+
maxLeaseMs: 86_400_000,
|
|
1085
|
+
};
|
|
1086
|
+
if (credentialProvider === 'windows-dpapi') {
|
|
1087
|
+
const path = input.credentialPath;
|
|
1088
|
+
if (path === undefined || path.length > 1_024 || path.includes('\0')
|
|
1089
|
+
|| (!isAbsolute(path) && !win32.isAbsolute(path))) {
|
|
1090
|
+
throw new Error('lark-channel setup: invalid credentialPath');
|
|
1091
|
+
}
|
|
1092
|
+
upsertById(document, handleList, { ...commonHandle, provider: credentialProvider, path });
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
upsertById(document, handleList, {
|
|
1096
|
+
...commonHandle,
|
|
1097
|
+
provider: credentialProvider,
|
|
1098
|
+
service: keychainService,
|
|
1099
|
+
account: keychainAccount,
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
const larkRow = ensureRow(document, rows, 'dsh-enhanced-lark-channel');
|
|
1103
|
+
const channel = asMap(larkRow.get('config', true), 'lark-channel config');
|
|
1104
|
+
channel.delete('appSecretEnv');
|
|
1105
|
+
const channelConfig = {
|
|
1106
|
+
enabled: true,
|
|
1107
|
+
account,
|
|
1108
|
+
tenant,
|
|
1109
|
+
appId,
|
|
1110
|
+
credentialHandle,
|
|
1111
|
+
credentialPurpose: 'connect',
|
|
1112
|
+
credentialLeaseMs: 86_400_000,
|
|
1113
|
+
domain: input.domain,
|
|
1114
|
+
requireMentionInGroups: true,
|
|
1115
|
+
showProgress: true,
|
|
1116
|
+
statusReactions: true,
|
|
1117
|
+
maxTextBytes: 65_536,
|
|
1118
|
+
staleAfterMs: 300_000,
|
|
1119
|
+
handshakeTimeoutMs: 15_000,
|
|
1120
|
+
imageDownloadTimeoutMs: 30_000,
|
|
1121
|
+
};
|
|
1122
|
+
for (const [key, value] of Object.entries(channelConfig))
|
|
1123
|
+
channel.set(key, value);
|
|
1124
|
+
return document.toString({ lineWidth: 0 });
|
|
1125
|
+
}
|
|
1126
|
+
//# sourceMappingURL=setup-profile.js.map
|