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