Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { localize } from '../../../../nls.js';
7
import { Event, Emitter } from '../../../../base/common/event.js';
8
import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
9
import { IExtensionManagementService, IExtensionIdentifier, IGlobalExtensionEnablementService, ENABLED_EXTENSIONS_STORAGE_PATH, DISABLED_EXTENSIONS_STORAGE_PATH, InstallOperation, IAllowedExtensionsService } from '../../../../platform/extensionManagement/common/extensionManagement.js';
10
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IWorkbenchExtensionManagementService, IExtensionManagementServer, ExtensionInstallLocation } from '../common/extensionManagement.js';
11
import { areSameExtensions, BetterMergeId, getExtensionDependencies, isMalicious } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
12
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
13
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
14
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
15
import { ExtensionType, IExtension, isAuthenticationProviderExtension, isLanguagePackExtension, isResolverExtension } from '../../../../platform/extensions/common/extensions.js';
16
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
17
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
18
import { StorageManager } from '../../../../platform/extensionManagement/common/extensionEnablementService.js';
19
import { webWorkerExtHostConfig, WebWorkerExtHostConfigValue } from '../../extensions/common/extensions.js';
20
import { IUserDataSyncAccountService } from '../../../../platform/userDataSync/common/userDataSyncAccount.js';
21
import { IUserDataSyncEnablementService } from '../../../../platform/userDataSync/common/userDataSync.js';
22
import { ILifecycleService, LifecyclePhase } from '../../lifecycle/common/lifecycle.js';
23
import { INotificationService, NotificationPriority, Severity } from '../../../../platform/notification/common/notification.js';
24
import { IHostService } from '../../host/browser/host.js';
25
import { IExtensionBisectService } from './extensionBisect.js';
26
import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from '../../../../platform/workspace/common/workspaceTrust.js';
27
import { IExtensionManifestPropertiesService } from '../../extensions/common/extensionManifestPropertiesService.js';
28
import { isVirtualWorkspace } from '../../../../platform/workspace/common/virtualWorkspace.js';
29
import { ILogService } from '../../../../platform/log/common/log.js';
30
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
31
import { equals } from '../../../../base/common/arrays.js';
32
import { isString } from '../../../../base/common/types.js';
33
import { Delayer } from '../../../../base/common/async.js';
34
35
const SOURCE = 'IWorkbenchExtensionEnablementService';
36
37
type WorkspaceType = { readonly virtual: boolean; readonly trusted: boolean };
38
39
export class ExtensionEnablementService extends Disposable implements IWorkbenchExtensionEnablementService {
40
41
declare readonly _serviceBrand: undefined;
42
43
private readonly _onEnablementChanged = new Emitter<readonly IExtension[]>();
44
public readonly onEnablementChanged: Event<readonly IExtension[]> = this._onEnablementChanged.event;
45
46
protected readonly extensionsManager: ExtensionsManager;
47
private readonly storageManager: StorageManager;
48
private extensionsDisabledExtensions: IExtension[] = [];
49
private readonly delayer = this._register(new Delayer<void>(0));
50
51
constructor(
52
@IStorageService private readonly storageService: IStorageService,
53
@IGlobalExtensionEnablementService protected readonly globalExtensionEnablementService: IGlobalExtensionEnablementService,
54
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
55
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
56
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
57
@IConfigurationService private readonly configurationService: IConfigurationService,
58
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
59
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
60
@IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService,
61
@ILifecycleService private readonly lifecycleService: ILifecycleService,
62
@INotificationService private readonly notificationService: INotificationService,
63
@IHostService hostService: IHostService,
64
@IExtensionBisectService private readonly extensionBisectService: IExtensionBisectService,
65
@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,
66
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
67
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
68
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
69
@IInstantiationService instantiationService: IInstantiationService,
70
@ILogService private readonly logService: ILogService,
71
) {
72
super();
73
this.storageManager = this._register(new StorageManager(storageService));
74
75
const uninstallDisposable = this._register(Event.filter(extensionManagementService.onDidUninstallExtension, e => !e.error)(({ identifier }) => this._reset(identifier)));
76
let isDisposed = false;
77
this._register(toDisposable(() => isDisposed = true));
78
this.extensionsManager = this._register(instantiationService.createInstance(ExtensionsManager));
79
this.extensionsManager.whenInitialized().then(() => {
80
if (!isDisposed) {
81
uninstallDisposable.dispose();
82
this._onDidChangeExtensions([], [], false);
83
this._register(this.extensionsManager.onDidChangeExtensions(({ added, removed, isProfileSwitch }) => this._onDidChangeExtensions(added, removed, isProfileSwitch)));
84
this.loopCheckForMaliciousExtensions();
85
}
86
});
87
88
this._register(this.globalExtensionEnablementService.onDidChangeEnablement(({ extensions, source }) => this._onDidChangeGloballyDisabledExtensions(extensions, source)));
89
this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this._onDidChangeExtensions([], [], false)));
90
91
// delay notification for extensions disabled until workbench restored
92
if (this.allUserExtensionsDisabled) {
93
this.lifecycleService.when(LifecyclePhase.Eventually).then(() => {
94
this.notificationService.prompt(Severity.Info, localize('extensionsDisabled', "All installed extensions are temporarily disabled."), [{
95
label: localize('Reload', "Reload and Enable Extensions"),
96
run: () => hostService.reload({ disableExtensions: false })
97
}], {
98
sticky: true,
99
priority: NotificationPriority.URGENT
100
});
101
});
102
}
103
}
104
105
private get hasWorkspace(): boolean {
106
return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY;
107
}
108
109
private get allUserExtensionsDisabled(): boolean {
110
return this.environmentService.disableExtensions === true;
111
}
112
113
getEnablementState(extension: IExtension): EnablementState {
114
return this._computeEnablementState(extension, this.extensionsManager.extensions, this.getWorkspaceType());
115
}
116
117
getEnablementStates(extensions: IExtension[], workspaceTypeOverrides: Partial<WorkspaceType> = {}): EnablementState[] {
118
const extensionsEnablements = new Map<IExtension, EnablementState>();
119
const workspaceType = { ...this.getWorkspaceType(), ...workspaceTypeOverrides };
120
return extensions.map(extension => this._computeEnablementState(extension, extensions, workspaceType, extensionsEnablements));
121
}
122
123
getDependenciesEnablementStates(extension: IExtension): [IExtension, EnablementState][] {
124
return getExtensionDependencies(this.extensionsManager.extensions, extension).map(e => [e, this.getEnablementState(e)]);
125
}
126
127
canChangeEnablement(extension: IExtension): boolean {
128
try {
129
this.throwErrorIfCannotChangeEnablement(extension);
130
return true;
131
} catch (error) {
132
return false;
133
}
134
}
135
136
canChangeWorkspaceEnablement(extension: IExtension): boolean {
137
if (!this.canChangeEnablement(extension)) {
138
return false;
139
}
140
141
try {
142
this.throwErrorIfCannotChangeWorkspaceEnablement(extension);
143
return true;
144
} catch (error) {
145
return false;
146
}
147
}
148
149
private throwErrorIfCannotChangeEnablement(extension: IExtension, donotCheckDependencies?: boolean): void {
150
if (isLanguagePackExtension(extension.manifest)) {
151
throw new Error(localize('cannot disable language pack extension', "Cannot change enablement of {0} extension because it contributes language packs.", extension.manifest.displayName || extension.identifier.id));
152
}
153
154
if (this.userDataSyncEnablementService.isEnabled() && this.userDataSyncAccountService.account &&
155
isAuthenticationProviderExtension(extension.manifest) && extension.manifest.contributes!.authentication!.some(a => a.id === this.userDataSyncAccountService.account!.authenticationProviderId)) {
156
throw new Error(localize('cannot disable auth extension', "Cannot change enablement {0} extension because Settings Sync depends on it.", extension.manifest.displayName || extension.identifier.id));
157
}
158
159
if (this._isEnabledInEnv(extension)) {
160
throw new Error(localize('cannot change enablement environment', "Cannot change enablement of {0} extension because it is enabled in environment", extension.manifest.displayName || extension.identifier.id));
161
}
162
163
this.throwErrorIfEnablementStateCannotBeChanged(extension, this.getEnablementState(extension), donotCheckDependencies);
164
}
165
166
private throwErrorIfEnablementStateCannotBeChanged(extension: IExtension, enablementStateOfExtension: EnablementState, donotCheckDependencies?: boolean): void {
167
switch (enablementStateOfExtension) {
168
case EnablementState.DisabledByEnvironment:
169
throw new Error(localize('cannot change disablement environment', "Cannot change enablement of {0} extension because it is disabled in environment", extension.manifest.displayName || extension.identifier.id));
170
case EnablementState.DisabledByMalicious:
171
throw new Error(localize('cannot change enablement malicious', "Cannot change enablement of {0} extension because it is malicious", extension.manifest.displayName || extension.identifier.id));
172
case EnablementState.DisabledByVirtualWorkspace:
173
throw new Error(localize('cannot change enablement virtual workspace', "Cannot change enablement of {0} extension because it does not support virtual workspaces", extension.manifest.displayName || extension.identifier.id));
174
case EnablementState.DisabledByExtensionKind:
175
throw new Error(localize('cannot change enablement extension kind', "Cannot change enablement of {0} extension because of its extension kind", extension.manifest.displayName || extension.identifier.id));
176
case EnablementState.DisabledByAllowlist:
177
throw new Error(localize('cannot change disallowed extension enablement', "Cannot change enablement of {0} extension because it is disallowed", extension.manifest.displayName || extension.identifier.id));
178
case EnablementState.DisabledByInvalidExtension:
179
throw new Error(localize('cannot change invalid extension enablement', "Cannot change enablement of {0} extension because of it is invalid", extension.manifest.displayName || extension.identifier.id));
180
case EnablementState.DisabledByExtensionDependency:
181
if (donotCheckDependencies) {
182
break;
183
}
184
// Can be changed only when all its dependencies enablements can be changed
185
for (const dependency of getExtensionDependencies(this.extensionsManager.extensions, extension)) {
186
if (this.isEnabled(dependency)) {
187
continue;
188
}
189
throw new Error(localize('cannot change enablement dependency', "Cannot enable '{0}' extension because it depends on '{1}' extension that cannot be enabled", extension.manifest.displayName || extension.identifier.id, dependency.manifest.displayName || dependency.identifier.id));
190
}
191
}
192
}
193
194
private throwErrorIfCannotChangeWorkspaceEnablement(extension: IExtension): void {
195
if (!this.hasWorkspace) {
196
throw new Error(localize('noWorkspace', "No workspace."));
197
}
198
if (isAuthenticationProviderExtension(extension.manifest)) {
199
throw new Error(localize('cannot disable auth extension in workspace', "Cannot change enablement of {0} extension in workspace because it contributes authentication providers", extension.manifest.displayName || extension.identifier.id));
200
}
201
}
202
203
async setEnablement(extensions: IExtension[], newState: EnablementState): Promise<boolean[]> {
204
await this.extensionsManager.whenInitialized();
205
206
if (newState === EnablementState.EnabledGlobally || newState === EnablementState.EnabledWorkspace) {
207
extensions.push(...this.getExtensionsToEnableRecursively(extensions, this.extensionsManager.extensions, newState, { dependencies: true, pack: true }));
208
}
209
210
const workspace = newState === EnablementState.DisabledWorkspace || newState === EnablementState.EnabledWorkspace;
211
for (const extension of extensions) {
212
if (workspace) {
213
this.throwErrorIfCannotChangeWorkspaceEnablement(extension);
214
} else {
215
this.throwErrorIfCannotChangeEnablement(extension);
216
}
217
}
218
219
const result: boolean[] = [];
220
for (const extension of extensions) {
221
const enablementState = this.getEnablementState(extension);
222
if (enablementState === EnablementState.DisabledByTrustRequirement
223
/* All its disabled dependencies are disabled by Trust Requirement */
224
|| (enablementState === EnablementState.DisabledByExtensionDependency && this.getDependenciesEnablementStates(extension).every(([, e]) => this.isEnabledEnablementState(e) || e === EnablementState.DisabledByTrustRequirement))
225
) {
226
const trustState = await this.workspaceTrustRequestService.requestWorkspaceTrust();
227
result.push(trustState ?? false);
228
} else {
229
result.push(await this._setUserEnablementState(extension, newState));
230
}
231
}
232
233
const changedExtensions = extensions.filter((e, index) => result[index]);
234
if (changedExtensions.length) {
235
this._onEnablementChanged.fire(changedExtensions);
236
}
237
return result;
238
}
239
240
private getExtensionsToEnableRecursively(extensions: IExtension[], allExtensions: ReadonlyArray<IExtension>, enablementState: EnablementState, options: { dependencies: boolean; pack: boolean }, checked: IExtension[] = []): IExtension[] {
241
if (!options.dependencies && !options.pack) {
242
return [];
243
}
244
245
const toCheck = extensions.filter(e => checked.indexOf(e) === -1);
246
if (!toCheck.length) {
247
return [];
248
}
249
250
for (const extension of toCheck) {
251
checked.push(extension);
252
}
253
254
const extensionsToEnable: IExtension[] = [];
255
for (const extension of allExtensions) {
256
// Extension is already checked
257
if (checked.some(e => areSameExtensions(e.identifier, extension.identifier))) {
258
continue;
259
}
260
261
const enablementStateOfExtension = this.getEnablementState(extension);
262
// Extension is enabled
263
if (this.isEnabledEnablementState(enablementStateOfExtension)) {
264
continue;
265
}
266
267
// Skip if dependency extension is disabled by extension kind
268
if (enablementStateOfExtension === EnablementState.DisabledByExtensionKind) {
269
continue;
270
}
271
272
// Check if the extension is a dependency or in extension pack
273
if (extensions.some(e =>
274
(options.dependencies && e.manifest.extensionDependencies?.some(id => areSameExtensions({ id }, extension.identifier)))
275
|| (options.pack && e.manifest.extensionPack?.some(id => areSameExtensions({ id }, extension.identifier))))) {
276
277
const index = extensionsToEnable.findIndex(e => areSameExtensions(e.identifier, extension.identifier));
278
279
// Extension is not added to the disablement list so add it
280
if (index === -1) {
281
extensionsToEnable.push(extension);
282
}
283
284
// Extension is there already in the disablement list.
285
else {
286
try {
287
// Replace only if the enablement state can be changed
288
this.throwErrorIfEnablementStateCannotBeChanged(extension, enablementStateOfExtension, true);
289
extensionsToEnable.splice(index, 1, extension);
290
} catch (error) { /*Do not add*/ }
291
}
292
}
293
}
294
295
if (extensionsToEnable.length) {
296
extensionsToEnable.push(...this.getExtensionsToEnableRecursively(extensionsToEnable, allExtensions, enablementState, options, checked));
297
}
298
299
return extensionsToEnable;
300
}
301
302
private _setUserEnablementState(extension: IExtension, newState: EnablementState): Promise<boolean> {
303
304
const currentState = this._getUserEnablementState(extension.identifier);
305
306
if (currentState === newState) {
307
return Promise.resolve(false);
308
}
309
310
switch (newState) {
311
case EnablementState.EnabledGlobally:
312
this._enableExtension(extension.identifier);
313
break;
314
case EnablementState.DisabledGlobally:
315
this._disableExtension(extension.identifier);
316
break;
317
case EnablementState.EnabledWorkspace:
318
this._enableExtensionInWorkspace(extension.identifier);
319
break;
320
case EnablementState.DisabledWorkspace:
321
this._disableExtensionInWorkspace(extension.identifier);
322
break;
323
}
324
325
return Promise.resolve(true);
326
}
327
328
isEnabled(extension: IExtension): boolean {
329
const enablementState = this.getEnablementState(extension);
330
return this.isEnabledEnablementState(enablementState);
331
}
332
333
isEnabledEnablementState(enablementState: EnablementState): boolean {
334
return enablementState === EnablementState.EnabledByEnvironment || enablementState === EnablementState.EnabledWorkspace || enablementState === EnablementState.EnabledGlobally;
335
}
336
337
isDisabledGlobally(extension: IExtension): boolean {
338
return this._isDisabledGlobally(extension.identifier);
339
}
340
341
private _computeEnablementState(extension: IExtension, extensions: ReadonlyArray<IExtension>, workspaceType: WorkspaceType, computedEnablementStates?: Map<IExtension, EnablementState>): EnablementState {
342
computedEnablementStates = computedEnablementStates ?? new Map<IExtension, EnablementState>();
343
let enablementState = computedEnablementStates.get(extension);
344
if (enablementState !== undefined) {
345
return enablementState;
346
}
347
348
enablementState = this._getUserEnablementState(extension.identifier);
349
const isEnabled = this.isEnabledEnablementState(enablementState);
350
351
if (isMalicious(extension.identifier, this.getMaliciousExtensions().map(e => ({ extensionOrPublisher: e })))) {
352
enablementState = EnablementState.DisabledByMalicious;
353
}
354
355
else if (isEnabled && extension.type === ExtensionType.User && this.allowedExtensionsService.isAllowed(extension) !== true) {
356
enablementState = EnablementState.DisabledByAllowlist;
357
}
358
359
else if (isEnabled && !extension.isValid) {
360
enablementState = EnablementState.DisabledByInvalidExtension;
361
}
362
363
else if (this.extensionBisectService.isDisabledByBisect(extension)) {
364
enablementState = EnablementState.DisabledByEnvironment;
365
}
366
367
else if (this._isDisabledInEnv(extension)) {
368
enablementState = EnablementState.DisabledByEnvironment;
369
}
370
371
else if (this._isDisabledByVirtualWorkspace(extension, workspaceType)) {
372
enablementState = EnablementState.DisabledByVirtualWorkspace;
373
}
374
375
else if (isEnabled && this._isDisabledByWorkspaceTrust(extension, workspaceType)) {
376
enablementState = EnablementState.DisabledByTrustRequirement;
377
}
378
379
else if (this._isDisabledByExtensionKind(extension)) {
380
enablementState = EnablementState.DisabledByExtensionKind;
381
}
382
383
else if (isEnabled && this._isDisabledByExtensionDependency(extension, extensions, workspaceType, computedEnablementStates)) {
384
enablementState = EnablementState.DisabledByExtensionDependency;
385
}
386
387
else if (!isEnabled && this._isEnabledInEnv(extension)) {
388
enablementState = EnablementState.EnabledByEnvironment;
389
}
390
391
computedEnablementStates.set(extension, enablementState);
392
return enablementState;
393
}
394
395
private _isDisabledInEnv(extension: IExtension): boolean {
396
if (this.allUserExtensionsDisabled) {
397
return !extension.isBuiltin && !isResolverExtension(extension.manifest, this.environmentService.remoteAuthority);
398
}
399
400
const disabledExtensions = this.environmentService.disableExtensions;
401
if (Array.isArray(disabledExtensions)) {
402
return disabledExtensions.some(id => areSameExtensions({ id }, extension.identifier));
403
}
404
405
// Check if this is the better merge extension which was migrated to a built-in extension
406
if (areSameExtensions({ id: BetterMergeId.value }, extension.identifier)) {
407
return true;
408
}
409
410
return false;
411
}
412
413
private _isEnabledInEnv(extension: IExtension): boolean {
414
const enabledExtensions = this.environmentService.enableExtensions;
415
if (Array.isArray(enabledExtensions)) {
416
return enabledExtensions.some(id => areSameExtensions({ id }, extension.identifier));
417
}
418
return false;
419
}
420
421
private _isDisabledByVirtualWorkspace(extension: IExtension, workspaceType: WorkspaceType): boolean {
422
// Not a virtual workspace
423
if (!workspaceType.virtual) {
424
return false;
425
}
426
427
// Supports virtual workspace
428
if (this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType(extension.manifest) !== false) {
429
return false;
430
}
431
432
// Web extension from web extension management server
433
if (this.extensionManagementServerService.getExtensionManagementServer(extension) === this.extensionManagementServerService.webExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWeb(extension.manifest)) {
434
return false;
435
}
436
437
return true;
438
}
439
440
private _isDisabledByExtensionKind(extension: IExtension): boolean {
441
if (this.extensionManagementServerService.remoteExtensionManagementServer || this.extensionManagementServerService.webExtensionManagementServer) {
442
const installLocation = this.extensionManagementServerService.getExtensionInstallLocation(extension);
443
for (const extensionKind of this.extensionManifestPropertiesService.getExtensionKind(extension.manifest)) {
444
if (extensionKind === 'ui') {
445
if (installLocation === ExtensionInstallLocation.Local) {
446
return false;
447
}
448
}
449
if (extensionKind === 'workspace') {
450
if (installLocation === ExtensionInstallLocation.Remote) {
451
return false;
452
}
453
}
454
if (extensionKind === 'web') {
455
if (this.extensionManagementServerService.webExtensionManagementServer /* web */) {
456
if (installLocation === ExtensionInstallLocation.Web || installLocation === ExtensionInstallLocation.Remote) {
457
return false;
458
}
459
} else if (installLocation === ExtensionInstallLocation.Local) {
460
const enableLocalWebWorker = this.configurationService.getValue<WebWorkerExtHostConfigValue>(webWorkerExtHostConfig);
461
if (enableLocalWebWorker === true || enableLocalWebWorker === 'auto') {
462
// Web extensions are enabled on all configurations
463
return false;
464
}
465
}
466
}
467
}
468
return true;
469
}
470
return false;
471
}
472
473
private _isDisabledByWorkspaceTrust(extension: IExtension, workspaceType: WorkspaceType): boolean {
474
if (workspaceType.trusted) {
475
return false;
476
}
477
478
if (this.contextService.isInsideWorkspace(extension.location)) {
479
return true;
480
}
481
482
return this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.manifest) === false;
483
}
484
485
private _isDisabledByExtensionDependency(extension: IExtension, extensions: ReadonlyArray<IExtension>, workspaceType: WorkspaceType, computedEnablementStates: Map<IExtension, EnablementState>): boolean {
486
487
if (!extension.manifest.extensionDependencies) {
488
return false;
489
}
490
491
// Find dependency that is from the same server or does not exports any API
492
const dependencyExtensions = extensions.filter(e =>
493
extension.manifest.extensionDependencies?.some(id => areSameExtensions(e.identifier, { id })
494
&& (this.extensionManagementServerService.getExtensionManagementServer(e) === this.extensionManagementServerService.getExtensionManagementServer(extension) || ((e.manifest.main || e.manifest.browser) && e.manifest.api === 'none'))));
495
496
if (!dependencyExtensions.length) {
497
return false;
498
}
499
500
const hasEnablementState = computedEnablementStates.has(extension);
501
if (!hasEnablementState) {
502
// Placeholder to handle cyclic deps
503
computedEnablementStates.set(extension, EnablementState.EnabledGlobally);
504
}
505
try {
506
for (const dependencyExtension of dependencyExtensions) {
507
const enablementState = this._computeEnablementState(dependencyExtension, extensions, workspaceType, computedEnablementStates);
508
if (!this.isEnabledEnablementState(enablementState) && enablementState !== EnablementState.DisabledByExtensionKind) {
509
return true;
510
}
511
}
512
} finally {
513
if (!hasEnablementState) {
514
// remove the placeholder
515
computedEnablementStates.delete(extension);
516
}
517
}
518
519
return false;
520
}
521
522
private _getUserEnablementState(identifier: IExtensionIdentifier): EnablementState {
523
if (this.hasWorkspace) {
524
if (this._getWorkspaceEnabledExtensions().filter(e => areSameExtensions(e, identifier))[0]) {
525
return EnablementState.EnabledWorkspace;
526
}
527
528
if (this._getWorkspaceDisabledExtensions().filter(e => areSameExtensions(e, identifier))[0]) {
529
return EnablementState.DisabledWorkspace;
530
}
531
}
532
if (this._isDisabledGlobally(identifier)) {
533
return EnablementState.DisabledGlobally;
534
}
535
return EnablementState.EnabledGlobally;
536
}
537
538
private _isDisabledGlobally(identifier: IExtensionIdentifier): boolean {
539
return this.globalExtensionEnablementService.getDisabledExtensions().some(e => areSameExtensions(e, identifier));
540
}
541
542
private _enableExtension(identifier: IExtensionIdentifier): Promise<boolean> {
543
this._removeFromWorkspaceDisabledExtensions(identifier);
544
this._removeFromWorkspaceEnabledExtensions(identifier);
545
return this.globalExtensionEnablementService.enableExtension(identifier, SOURCE);
546
}
547
548
private _disableExtension(identifier: IExtensionIdentifier): Promise<boolean> {
549
this._removeFromWorkspaceDisabledExtensions(identifier);
550
this._removeFromWorkspaceEnabledExtensions(identifier);
551
return this.globalExtensionEnablementService.disableExtension(identifier, SOURCE);
552
}
553
554
private _enableExtensionInWorkspace(identifier: IExtensionIdentifier): void {
555
this._removeFromWorkspaceDisabledExtensions(identifier);
556
this._addToWorkspaceEnabledExtensions(identifier);
557
}
558
559
private _disableExtensionInWorkspace(identifier: IExtensionIdentifier): void {
560
this._addToWorkspaceDisabledExtensions(identifier);
561
this._removeFromWorkspaceEnabledExtensions(identifier);
562
}
563
564
private _addToWorkspaceDisabledExtensions(identifier: IExtensionIdentifier): Promise<boolean> {
565
if (!this.hasWorkspace) {
566
return Promise.resolve(false);
567
}
568
const disabledExtensions = this._getWorkspaceDisabledExtensions();
569
if (disabledExtensions.every(e => !areSameExtensions(e, identifier))) {
570
disabledExtensions.push(identifier);
571
this._setDisabledExtensions(disabledExtensions);
572
return Promise.resolve(true);
573
}
574
return Promise.resolve(false);
575
}
576
577
private async _removeFromWorkspaceDisabledExtensions(identifier: IExtensionIdentifier): Promise<boolean> {
578
if (!this.hasWorkspace) {
579
return false;
580
}
581
const disabledExtensions = this._getWorkspaceDisabledExtensions();
582
for (let index = 0; index < disabledExtensions.length; index++) {
583
const disabledExtension = disabledExtensions[index];
584
if (areSameExtensions(disabledExtension, identifier)) {
585
disabledExtensions.splice(index, 1);
586
this._setDisabledExtensions(disabledExtensions);
587
return true;
588
}
589
}
590
return false;
591
}
592
593
private _addToWorkspaceEnabledExtensions(identifier: IExtensionIdentifier): boolean {
594
if (!this.hasWorkspace) {
595
return false;
596
}
597
const enabledExtensions = this._getWorkspaceEnabledExtensions();
598
if (enabledExtensions.every(e => !areSameExtensions(e, identifier))) {
599
enabledExtensions.push(identifier);
600
this._setEnabledExtensions(enabledExtensions);
601
return true;
602
}
603
return false;
604
}
605
606
private _removeFromWorkspaceEnabledExtensions(identifier: IExtensionIdentifier): boolean {
607
if (!this.hasWorkspace) {
608
return false;
609
}
610
const enabledExtensions = this._getWorkspaceEnabledExtensions();
611
for (let index = 0; index < enabledExtensions.length; index++) {
612
const disabledExtension = enabledExtensions[index];
613
if (areSameExtensions(disabledExtension, identifier)) {
614
enabledExtensions.splice(index, 1);
615
this._setEnabledExtensions(enabledExtensions);
616
return true;
617
}
618
}
619
return false;
620
}
621
622
protected _getWorkspaceEnabledExtensions(): IExtensionIdentifier[] {
623
return this._getExtensions(ENABLED_EXTENSIONS_STORAGE_PATH);
624
}
625
626
private _setEnabledExtensions(enabledExtensions: IExtensionIdentifier[]): void {
627
this._setExtensions(ENABLED_EXTENSIONS_STORAGE_PATH, enabledExtensions);
628
}
629
630
protected _getWorkspaceDisabledExtensions(): IExtensionIdentifier[] {
631
return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH);
632
}
633
634
private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[]): void {
635
this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions);
636
}
637
638
private _getExtensions(storageId: string): IExtensionIdentifier[] {
639
if (!this.hasWorkspace) {
640
return [];
641
}
642
return this.storageManager.get(storageId, StorageScope.WORKSPACE);
643
}
644
645
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void {
646
this.storageManager.set(storageId, extensions, StorageScope.WORKSPACE);
647
}
648
649
private async _onDidChangeGloballyDisabledExtensions(extensionIdentifiers: ReadonlyArray<IExtensionIdentifier>, source?: string): Promise<void> {
650
if (source !== SOURCE) {
651
await this.extensionsManager.whenInitialized();
652
const extensions = this.extensionsManager.extensions.filter(installedExtension => extensionIdentifiers.some(identifier => areSameExtensions(identifier, installedExtension.identifier)));
653
this._onEnablementChanged.fire(extensions);
654
}
655
}
656
657
private _onDidChangeExtensions(added: ReadonlyArray<IExtension>, removed: ReadonlyArray<IExtension>, isProfileSwitch: boolean): void {
658
const changedExtensions: IExtension[] = added.filter(e => !this.isEnabledEnablementState(this.getEnablementState(e)));
659
const existingDisabledExtensions = this.extensionsDisabledExtensions;
660
this.extensionsDisabledExtensions = this.extensionsManager.extensions.filter(extension => {
661
const enablementState = this.getEnablementState(extension);
662
return enablementState === EnablementState.DisabledByExtensionDependency || enablementState === EnablementState.DisabledByAllowlist || enablementState === EnablementState.DisabledByMalicious;
663
});
664
for (const extension of existingDisabledExtensions) {
665
if (this.extensionsDisabledExtensions.every(e => !areSameExtensions(e.identifier, extension.identifier))) {
666
changedExtensions.push(extension);
667
}
668
}
669
for (const extension of this.extensionsDisabledExtensions) {
670
if (existingDisabledExtensions.every(e => !areSameExtensions(e.identifier, extension.identifier))) {
671
changedExtensions.push(extension);
672
}
673
}
674
if (changedExtensions.length) {
675
this._onEnablementChanged.fire(changedExtensions);
676
}
677
if (!isProfileSwitch) {
678
removed.forEach(({ identifier }) => this._reset(identifier));
679
}
680
}
681
682
public async updateExtensionsEnablementsWhenWorkspaceTrustChanges(): Promise<void> {
683
await this.extensionsManager.whenInitialized();
684
685
const computeEnablementStates = (workspaceType: WorkspaceType): [IExtension, EnablementState][] => {
686
const extensionsEnablements = new Map<IExtension, EnablementState>();
687
return this.extensionsManager.extensions.map(extension => [extension, this._computeEnablementState(extension, this.extensionsManager.extensions, workspaceType, extensionsEnablements)]);
688
};
689
690
const workspaceType = this.getWorkspaceType();
691
const enablementStatesWithTrustedWorkspace = computeEnablementStates({ ...workspaceType, trusted: true });
692
const enablementStatesWithUntrustedWorkspace = computeEnablementStates({ ...workspaceType, trusted: false });
693
const enablementChangedExtensionsBecauseOfTrust = enablementStatesWithTrustedWorkspace.filter(([, enablementState], index) => enablementState !== enablementStatesWithUntrustedWorkspace[index][1]).map(([extension]) => extension);
694
695
if (enablementChangedExtensionsBecauseOfTrust.length) {
696
this._onEnablementChanged.fire(enablementChangedExtensionsBecauseOfTrust);
697
}
698
}
699
700
private getWorkspaceType(): WorkspaceType {
701
return { trusted: this.workspaceTrustManagementService.isWorkspaceTrusted(), virtual: isVirtualWorkspace(this.contextService.getWorkspace()) };
702
}
703
704
private _reset(extension: IExtensionIdentifier) {
705
this._removeFromWorkspaceDisabledExtensions(extension);
706
this._removeFromWorkspaceEnabledExtensions(extension);
707
this.globalExtensionEnablementService.enableExtension(extension);
708
}
709
710
private loopCheckForMaliciousExtensions(): void {
711
this.checkForMaliciousExtensions()
712
.then(() => this.delayer.trigger(() => { }, 1000 * 60 * 5)) // every five minutes
713
.then(() => this.loopCheckForMaliciousExtensions());
714
}
715
716
private async checkForMaliciousExtensions(): Promise<void> {
717
try {
718
const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();
719
const changed = this.storeMaliciousExtensions(extensionsControlManifest.malicious.map(({ extensionOrPublisher }) => extensionOrPublisher));
720
if (changed) {
721
this._onDidChangeExtensions([], [], false);
722
}
723
} catch (err) {
724
this.logService.error(err);
725
}
726
}
727
728
private getMaliciousExtensions(): ReadonlyArray<IExtensionIdentifier | string> {
729
return this.storageService.getObject('extensionsEnablement/malicious', StorageScope.APPLICATION, []);
730
}
731
732
private storeMaliciousExtensions(extensions: ReadonlyArray<IExtensionIdentifier | string>): boolean {
733
const existing = this.getMaliciousExtensions();
734
if (equals(existing, extensions, (a, b) => !isString(a) && !isString(b) ? areSameExtensions(a, b) : a === b)) {
735
return false;
736
}
737
this.storageService.store('extensionsEnablement/malicious', JSON.stringify(extensions), StorageScope.APPLICATION, StorageTarget.MACHINE);
738
return true;
739
}
740
}
741
742
class ExtensionsManager extends Disposable {
743
744
private _extensions: IExtension[] = [];
745
get extensions(): readonly IExtension[] { return this._extensions; }
746
747
private _onDidChangeExtensions = this._register(new Emitter<{ added: readonly IExtension[]; removed: readonly IExtension[]; readonly isProfileSwitch: boolean }>());
748
readonly onDidChangeExtensions = this._onDidChangeExtensions.event;
749
750
private readonly initializePromise;
751
private disposed: boolean = false;
752
753
constructor(
754
@IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService,
755
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
756
@ILogService private readonly logService: ILogService
757
) {
758
super();
759
this._register(toDisposable(() => this.disposed = true));
760
this.initializePromise = this.initialize();
761
}
762
763
whenInitialized(): Promise<void> {
764
return this.initializePromise;
765
}
766
767
private async initialize(): Promise<void> {
768
try {
769
this._extensions = [
770
...await this.extensionManagementService.getInstalled(),
771
...await this.extensionManagementService.getInstalledWorkspaceExtensions(true)
772
];
773
if (this.disposed) {
774
return;
775
}
776
this._onDidChangeExtensions.fire({ added: this.extensions, removed: [], isProfileSwitch: false });
777
} catch (error) {
778
this.logService.error(error);
779
}
780
this._register(this.extensionManagementService.onDidInstallExtensions(e =>
781
this.updateExtensions(e.reduce<IExtension[]>((result, { local, operation }) => {
782
if (local && operation !== InstallOperation.Migrate) { result.push(local); } return result;
783
}, []), [], undefined, false)));
784
this._register(Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error))(e => this.updateExtensions([], [e.identifier], e.server, false)));
785
this._register(this.extensionManagementService.onDidChangeProfile(({ added, removed, server }) => {
786
this.updateExtensions(added, removed.map(({ identifier }) => identifier), server, true);
787
}));
788
}
789
790
private updateExtensions(added: IExtension[], identifiers: IExtensionIdentifier[], server: IExtensionManagementServer | undefined, isProfileSwitch: boolean): void {
791
if (added.length) {
792
for (const extension of added) {
793
const extensionServer = this.extensionManagementServerService.getExtensionManagementServer(extension);
794
const index = this._extensions.findIndex(e => areSameExtensions(e.identifier, extension.identifier) && this.extensionManagementServerService.getExtensionManagementServer(e) === extensionServer);
795
if (index !== -1) {
796
this._extensions.splice(index, 1);
797
}
798
}
799
this._extensions.push(...added);
800
}
801
const removed: IExtension[] = [];
802
for (const identifier of identifiers) {
803
const index = this._extensions.findIndex(e => areSameExtensions(e.identifier, identifier) && this.extensionManagementServerService.getExtensionManagementServer(e) === server);
804
if (index !== -1) {
805
removed.push(...this._extensions.splice(index, 1));
806
}
807
}
808
if (added.length || removed.length) {
809
this._onDidChangeExtensions.fire({ added, removed, isProfileSwitch });
810
}
811
}
812
}
813
814
registerSingleton(IWorkbenchExtensionEnablementService, ExtensionEnablementService, InstantiationType.Delayed);
815
816