Path: blob/main/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { localize } from '../../../../nls.js';6import { Event, Emitter } from '../../../../base/common/event.js';7import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';8import { IExtensionManagementService, IExtensionIdentifier, IGlobalExtensionEnablementService, ENABLED_EXTENSIONS_STORAGE_PATH, DISABLED_EXTENSIONS_STORAGE_PATH, InstallOperation, IAllowedExtensionsService } from '../../../../platform/extensionManagement/common/extensionManagement.js';9import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IWorkbenchExtensionManagementService, IExtensionManagementServer, ExtensionInstallLocation } from '../common/extensionManagement.js';10import { areSameExtensions, BetterMergeId, getExtensionDependencies, isMalicious } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';11import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';12import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';13import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';14import { ExtensionType, IExtension, isAuthenticationProviderExtension, isLanguagePackExtension, isResolverExtension } from '../../../../platform/extensions/common/extensions.js';15import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';16import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';17import { StorageManager } from '../../../../platform/extensionManagement/common/extensionEnablementService.js';18import { webWorkerExtHostConfig, WebWorkerExtHostConfigValue } from '../../extensions/common/extensions.js';19import { IUserDataSyncAccountService } from '../../../../platform/userDataSync/common/userDataSyncAccount.js';20import { IUserDataSyncEnablementService } from '../../../../platform/userDataSync/common/userDataSync.js';21import { ILifecycleService, LifecyclePhase } from '../../lifecycle/common/lifecycle.js';22import { INotificationService, NotificationPriority, Severity } from '../../../../platform/notification/common/notification.js';23import { IHostService } from '../../host/browser/host.js';24import { IExtensionBisectService } from './extensionBisect.js';25import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from '../../../../platform/workspace/common/workspaceTrust.js';26import { IExtensionManifestPropertiesService } from '../../extensions/common/extensionManifestPropertiesService.js';27import { isVirtualWorkspace } from '../../../../platform/workspace/common/virtualWorkspace.js';28import { ILogService } from '../../../../platform/log/common/log.js';29import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';30import { equals } from '../../../../base/common/arrays.js';31import { isString } from '../../../../base/common/types.js';32import { Delayer } from '../../../../base/common/async.js';3334const SOURCE = 'IWorkbenchExtensionEnablementService';3536type WorkspaceType = { readonly virtual: boolean; readonly trusted: boolean };3738export class ExtensionEnablementService extends Disposable implements IWorkbenchExtensionEnablementService {3940declare readonly _serviceBrand: undefined;4142private readonly _onEnablementChanged = new Emitter<readonly IExtension[]>();43public readonly onEnablementChanged: Event<readonly IExtension[]> = this._onEnablementChanged.event;4445protected readonly extensionsManager: ExtensionsManager;46private readonly storageManager: StorageManager;47private extensionsDisabledExtensions: IExtension[] = [];48private readonly delayer = this._register(new Delayer<void>(0));4950constructor(51@IStorageService private readonly storageService: IStorageService,52@IGlobalExtensionEnablementService protected readonly globalExtensionEnablementService: IGlobalExtensionEnablementService,53@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,54@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,55@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,56@IConfigurationService private readonly configurationService: IConfigurationService,57@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,58@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,59@IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService,60@ILifecycleService private readonly lifecycleService: ILifecycleService,61@INotificationService private readonly notificationService: INotificationService,62@IHostService hostService: IHostService,63@IExtensionBisectService private readonly extensionBisectService: IExtensionBisectService,64@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,65@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,66@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,67@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,68@IInstantiationService instantiationService: IInstantiationService,69@ILogService private readonly logService: ILogService,70) {71super();72this.storageManager = this._register(new StorageManager(storageService));7374const uninstallDisposable = this._register(Event.filter(extensionManagementService.onDidUninstallExtension, e => !e.error)(({ identifier }) => this._reset(identifier)));75let isDisposed = false;76this._register(toDisposable(() => isDisposed = true));77this.extensionsManager = this._register(instantiationService.createInstance(ExtensionsManager));78this.extensionsManager.whenInitialized().then(() => {79if (!isDisposed) {80uninstallDisposable.dispose();81this._onDidChangeExtensions([], [], false);82this._register(this.extensionsManager.onDidChangeExtensions(({ added, removed, isProfileSwitch }) => this._onDidChangeExtensions(added, removed, isProfileSwitch)));83this.loopCheckForMaliciousExtensions();84}85});8687this._register(this.globalExtensionEnablementService.onDidChangeEnablement(({ extensions, source }) => this._onDidChangeGloballyDisabledExtensions(extensions, source)));88this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this._onDidChangeExtensions([], [], false)));8990// delay notification for extensions disabled until workbench restored91if (this.allUserExtensionsDisabled) {92this.lifecycleService.when(LifecyclePhase.Eventually).then(() => {93this.notificationService.prompt(Severity.Info, localize('extensionsDisabled', "All installed extensions are temporarily disabled."), [{94label: localize('Reload', "Reload and Enable Extensions"),95run: () => hostService.reload({ disableExtensions: false })96}], {97sticky: true,98priority: NotificationPriority.URGENT99});100});101}102}103104private get hasWorkspace(): boolean {105return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY;106}107108private get allUserExtensionsDisabled(): boolean {109return this.environmentService.disableExtensions === true;110}111112getEnablementState(extension: IExtension): EnablementState {113return this._computeEnablementState(extension, this.extensionsManager.extensions, this.getWorkspaceType());114}115116getEnablementStates(extensions: IExtension[], workspaceTypeOverrides: Partial<WorkspaceType> = {}): EnablementState[] {117const extensionsEnablements = new Map<IExtension, EnablementState>();118const workspaceType = { ...this.getWorkspaceType(), ...workspaceTypeOverrides };119return extensions.map(extension => this._computeEnablementState(extension, extensions, workspaceType, extensionsEnablements));120}121122getDependenciesEnablementStates(extension: IExtension): [IExtension, EnablementState][] {123return getExtensionDependencies(this.extensionsManager.extensions, extension).map(e => [e, this.getEnablementState(e)]);124}125126canChangeEnablement(extension: IExtension): boolean {127try {128this.throwErrorIfCannotChangeEnablement(extension);129return true;130} catch (error) {131return false;132}133}134135canChangeWorkspaceEnablement(extension: IExtension): boolean {136if (!this.canChangeEnablement(extension)) {137return false;138}139140try {141this.throwErrorIfCannotChangeWorkspaceEnablement(extension);142return true;143} catch (error) {144return false;145}146}147148private throwErrorIfCannotChangeEnablement(extension: IExtension, donotCheckDependencies?: boolean): void {149if (isLanguagePackExtension(extension.manifest)) {150throw 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));151}152153if (this.userDataSyncEnablementService.isEnabled() && this.userDataSyncAccountService.account &&154isAuthenticationProviderExtension(extension.manifest) && extension.manifest.contributes!.authentication!.some(a => a.id === this.userDataSyncAccountService.account!.authenticationProviderId)) {155throw new Error(localize('cannot disable auth extension', "Cannot change enablement {0} extension because Settings Sync depends on it.", extension.manifest.displayName || extension.identifier.id));156}157158if (this._isEnabledInEnv(extension)) {159throw 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));160}161162this.throwErrorIfEnablementStateCannotBeChanged(extension, this.getEnablementState(extension), donotCheckDependencies);163}164165private throwErrorIfEnablementStateCannotBeChanged(extension: IExtension, enablementStateOfExtension: EnablementState, donotCheckDependencies?: boolean): void {166switch (enablementStateOfExtension) {167case EnablementState.DisabledByEnvironment:168throw 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));169case EnablementState.DisabledByMalicious:170throw new Error(localize('cannot change enablement malicious', "Cannot change enablement of {0} extension because it is malicious", extension.manifest.displayName || extension.identifier.id));171case EnablementState.DisabledByVirtualWorkspace:172throw 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));173case EnablementState.DisabledByExtensionKind:174throw 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));175case EnablementState.DisabledByAllowlist:176throw new Error(localize('cannot change disallowed extension enablement', "Cannot change enablement of {0} extension because it is disallowed", extension.manifest.displayName || extension.identifier.id));177case EnablementState.DisabledByInvalidExtension:178throw 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));179case EnablementState.DisabledByExtensionDependency:180if (donotCheckDependencies) {181break;182}183// Can be changed only when all its dependencies enablements can be changed184for (const dependency of getExtensionDependencies(this.extensionsManager.extensions, extension)) {185if (this.isEnabled(dependency)) {186continue;187}188throw 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));189}190}191}192193private throwErrorIfCannotChangeWorkspaceEnablement(extension: IExtension): void {194if (!this.hasWorkspace) {195throw new Error(localize('noWorkspace', "No workspace."));196}197if (isAuthenticationProviderExtension(extension.manifest)) {198throw 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));199}200}201202async setEnablement(extensions: IExtension[], newState: EnablementState): Promise<boolean[]> {203await this.extensionsManager.whenInitialized();204205if (newState === EnablementState.EnabledGlobally || newState === EnablementState.EnabledWorkspace) {206extensions.push(...this.getExtensionsToEnableRecursively(extensions, this.extensionsManager.extensions, newState, { dependencies: true, pack: true }));207}208209const workspace = newState === EnablementState.DisabledWorkspace || newState === EnablementState.EnabledWorkspace;210for (const extension of extensions) {211if (workspace) {212this.throwErrorIfCannotChangeWorkspaceEnablement(extension);213} else {214this.throwErrorIfCannotChangeEnablement(extension);215}216}217218const result: boolean[] = [];219for (const extension of extensions) {220const enablementState = this.getEnablementState(extension);221if (enablementState === EnablementState.DisabledByTrustRequirement222/* All its disabled dependencies are disabled by Trust Requirement */223|| (enablementState === EnablementState.DisabledByExtensionDependency && this.getDependenciesEnablementStates(extension).every(([, e]) => this.isEnabledEnablementState(e) || e === EnablementState.DisabledByTrustRequirement))224) {225const trustState = await this.workspaceTrustRequestService.requestWorkspaceTrust();226result.push(trustState ?? false);227} else {228result.push(await this._setUserEnablementState(extension, newState));229}230}231232const changedExtensions = extensions.filter((e, index) => result[index]);233if (changedExtensions.length) {234this._onEnablementChanged.fire(changedExtensions);235}236return result;237}238239private getExtensionsToEnableRecursively(extensions: IExtension[], allExtensions: ReadonlyArray<IExtension>, enablementState: EnablementState, options: { dependencies: boolean; pack: boolean }, checked: IExtension[] = []): IExtension[] {240if (!options.dependencies && !options.pack) {241return [];242}243244const toCheck = extensions.filter(e => checked.indexOf(e) === -1);245if (!toCheck.length) {246return [];247}248249for (const extension of toCheck) {250checked.push(extension);251}252253const extensionsToEnable: IExtension[] = [];254for (const extension of allExtensions) {255// Extension is already checked256if (checked.some(e => areSameExtensions(e.identifier, extension.identifier))) {257continue;258}259260const enablementStateOfExtension = this.getEnablementState(extension);261// Extension is enabled262if (this.isEnabledEnablementState(enablementStateOfExtension)) {263continue;264}265266// Skip if dependency extension is disabled by extension kind267if (enablementStateOfExtension === EnablementState.DisabledByExtensionKind) {268continue;269}270271// Check if the extension is a dependency or in extension pack272if (extensions.some(e =>273(options.dependencies && e.manifest.extensionDependencies?.some(id => areSameExtensions({ id }, extension.identifier)))274|| (options.pack && e.manifest.extensionPack?.some(id => areSameExtensions({ id }, extension.identifier))))) {275276const index = extensionsToEnable.findIndex(e => areSameExtensions(e.identifier, extension.identifier));277278// Extension is not added to the disablement list so add it279if (index === -1) {280extensionsToEnable.push(extension);281}282283// Extension is there already in the disablement list.284else {285try {286// Replace only if the enablement state can be changed287this.throwErrorIfEnablementStateCannotBeChanged(extension, enablementStateOfExtension, true);288extensionsToEnable.splice(index, 1, extension);289} catch (error) { /*Do not add*/ }290}291}292}293294if (extensionsToEnable.length) {295extensionsToEnable.push(...this.getExtensionsToEnableRecursively(extensionsToEnable, allExtensions, enablementState, options, checked));296}297298return extensionsToEnable;299}300301private _setUserEnablementState(extension: IExtension, newState: EnablementState): Promise<boolean> {302303const currentState = this._getUserEnablementState(extension.identifier);304305if (currentState === newState) {306return Promise.resolve(false);307}308309switch (newState) {310case EnablementState.EnabledGlobally:311this._enableExtension(extension.identifier);312break;313case EnablementState.DisabledGlobally:314this._disableExtension(extension.identifier);315break;316case EnablementState.EnabledWorkspace:317this._enableExtensionInWorkspace(extension.identifier);318break;319case EnablementState.DisabledWorkspace:320this._disableExtensionInWorkspace(extension.identifier);321break;322}323324return Promise.resolve(true);325}326327isEnabled(extension: IExtension): boolean {328const enablementState = this.getEnablementState(extension);329return this.isEnabledEnablementState(enablementState);330}331332isEnabledEnablementState(enablementState: EnablementState): boolean {333return enablementState === EnablementState.EnabledByEnvironment || enablementState === EnablementState.EnabledWorkspace || enablementState === EnablementState.EnabledGlobally;334}335336isDisabledGlobally(extension: IExtension): boolean {337return this._isDisabledGlobally(extension.identifier);338}339340private _computeEnablementState(extension: IExtension, extensions: ReadonlyArray<IExtension>, workspaceType: WorkspaceType, computedEnablementStates?: Map<IExtension, EnablementState>): EnablementState {341computedEnablementStates = computedEnablementStates ?? new Map<IExtension, EnablementState>();342let enablementState = computedEnablementStates.get(extension);343if (enablementState !== undefined) {344return enablementState;345}346347enablementState = this._getUserEnablementState(extension.identifier);348const isEnabled = this.isEnabledEnablementState(enablementState);349350if (isMalicious(extension.identifier, this.getMaliciousExtensions().map(e => ({ extensionOrPublisher: e })))) {351enablementState = EnablementState.DisabledByMalicious;352}353354else if (isEnabled && extension.type === ExtensionType.User && this.allowedExtensionsService.isAllowed(extension) !== true) {355enablementState = EnablementState.DisabledByAllowlist;356}357358else if (isEnabled && !extension.isValid) {359enablementState = EnablementState.DisabledByInvalidExtension;360}361362else if (this.extensionBisectService.isDisabledByBisect(extension)) {363enablementState = EnablementState.DisabledByEnvironment;364}365366else if (this._isDisabledInEnv(extension)) {367enablementState = EnablementState.DisabledByEnvironment;368}369370else if (this._isDisabledByVirtualWorkspace(extension, workspaceType)) {371enablementState = EnablementState.DisabledByVirtualWorkspace;372}373374else if (isEnabled && this._isDisabledByWorkspaceTrust(extension, workspaceType)) {375enablementState = EnablementState.DisabledByTrustRequirement;376}377378else if (this._isDisabledByExtensionKind(extension)) {379enablementState = EnablementState.DisabledByExtensionKind;380}381382else if (isEnabled && this._isDisabledByExtensionDependency(extension, extensions, workspaceType, computedEnablementStates)) {383enablementState = EnablementState.DisabledByExtensionDependency;384}385386else if (!isEnabled && this._isEnabledInEnv(extension)) {387enablementState = EnablementState.EnabledByEnvironment;388}389390computedEnablementStates.set(extension, enablementState);391return enablementState;392}393394private _isDisabledInEnv(extension: IExtension): boolean {395if (this.allUserExtensionsDisabled) {396return !extension.isBuiltin && !isResolverExtension(extension.manifest, this.environmentService.remoteAuthority);397}398399const disabledExtensions = this.environmentService.disableExtensions;400if (Array.isArray(disabledExtensions)) {401return disabledExtensions.some(id => areSameExtensions({ id }, extension.identifier));402}403404// Check if this is the better merge extension which was migrated to a built-in extension405if (areSameExtensions({ id: BetterMergeId.value }, extension.identifier)) {406return true;407}408409return false;410}411412private _isEnabledInEnv(extension: IExtension): boolean {413const enabledExtensions = this.environmentService.enableExtensions;414if (Array.isArray(enabledExtensions)) {415return enabledExtensions.some(id => areSameExtensions({ id }, extension.identifier));416}417return false;418}419420private _isDisabledByVirtualWorkspace(extension: IExtension, workspaceType: WorkspaceType): boolean {421// Not a virtual workspace422if (!workspaceType.virtual) {423return false;424}425426// Supports virtual workspace427if (this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType(extension.manifest) !== false) {428return false;429}430431// Web extension from web extension management server432if (this.extensionManagementServerService.getExtensionManagementServer(extension) === this.extensionManagementServerService.webExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWeb(extension.manifest)) {433return false;434}435436return true;437}438439private _isDisabledByExtensionKind(extension: IExtension): boolean {440if (this.extensionManagementServerService.remoteExtensionManagementServer || this.extensionManagementServerService.webExtensionManagementServer) {441const installLocation = this.extensionManagementServerService.getExtensionInstallLocation(extension);442for (const extensionKind of this.extensionManifestPropertiesService.getExtensionKind(extension.manifest)) {443if (extensionKind === 'ui') {444if (installLocation === ExtensionInstallLocation.Local) {445return false;446}447}448if (extensionKind === 'workspace') {449if (installLocation === ExtensionInstallLocation.Remote) {450return false;451}452}453if (extensionKind === 'web') {454if (this.extensionManagementServerService.webExtensionManagementServer /* web */) {455if (installLocation === ExtensionInstallLocation.Web || installLocation === ExtensionInstallLocation.Remote) {456return false;457}458} else if (installLocation === ExtensionInstallLocation.Local) {459const enableLocalWebWorker = this.configurationService.getValue<WebWorkerExtHostConfigValue>(webWorkerExtHostConfig);460if (enableLocalWebWorker === true || enableLocalWebWorker === 'auto') {461// Web extensions are enabled on all configurations462return false;463}464}465}466}467return true;468}469return false;470}471472private _isDisabledByWorkspaceTrust(extension: IExtension, workspaceType: WorkspaceType): boolean {473if (workspaceType.trusted) {474return false;475}476477if (this.contextService.isInsideWorkspace(extension.location)) {478return true;479}480481return this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.manifest) === false;482}483484private _isDisabledByExtensionDependency(extension: IExtension, extensions: ReadonlyArray<IExtension>, workspaceType: WorkspaceType, computedEnablementStates: Map<IExtension, EnablementState>): boolean {485486if (!extension.manifest.extensionDependencies) {487return false;488}489490// Find dependency that is from the same server or does not exports any API491const dependencyExtensions = extensions.filter(e =>492extension.manifest.extensionDependencies?.some(id => areSameExtensions(e.identifier, { id })493&& (this.extensionManagementServerService.getExtensionManagementServer(e) === this.extensionManagementServerService.getExtensionManagementServer(extension) || ((e.manifest.main || e.manifest.browser) && e.manifest.api === 'none'))));494495if (!dependencyExtensions.length) {496return false;497}498499const hasEnablementState = computedEnablementStates.has(extension);500if (!hasEnablementState) {501// Placeholder to handle cyclic deps502computedEnablementStates.set(extension, EnablementState.EnabledGlobally);503}504try {505for (const dependencyExtension of dependencyExtensions) {506const enablementState = this._computeEnablementState(dependencyExtension, extensions, workspaceType, computedEnablementStates);507if (!this.isEnabledEnablementState(enablementState) && enablementState !== EnablementState.DisabledByExtensionKind) {508return true;509}510}511} finally {512if (!hasEnablementState) {513// remove the placeholder514computedEnablementStates.delete(extension);515}516}517518return false;519}520521private _getUserEnablementState(identifier: IExtensionIdentifier): EnablementState {522if (this.hasWorkspace) {523if (this._getWorkspaceEnabledExtensions().filter(e => areSameExtensions(e, identifier))[0]) {524return EnablementState.EnabledWorkspace;525}526527if (this._getWorkspaceDisabledExtensions().filter(e => areSameExtensions(e, identifier))[0]) {528return EnablementState.DisabledWorkspace;529}530}531if (this._isDisabledGlobally(identifier)) {532return EnablementState.DisabledGlobally;533}534return EnablementState.EnabledGlobally;535}536537private _isDisabledGlobally(identifier: IExtensionIdentifier): boolean {538return this.globalExtensionEnablementService.getDisabledExtensions().some(e => areSameExtensions(e, identifier));539}540541private _enableExtension(identifier: IExtensionIdentifier): Promise<boolean> {542this._removeFromWorkspaceDisabledExtensions(identifier);543this._removeFromWorkspaceEnabledExtensions(identifier);544return this.globalExtensionEnablementService.enableExtension(identifier, SOURCE);545}546547private _disableExtension(identifier: IExtensionIdentifier): Promise<boolean> {548this._removeFromWorkspaceDisabledExtensions(identifier);549this._removeFromWorkspaceEnabledExtensions(identifier);550return this.globalExtensionEnablementService.disableExtension(identifier, SOURCE);551}552553private _enableExtensionInWorkspace(identifier: IExtensionIdentifier): void {554this._removeFromWorkspaceDisabledExtensions(identifier);555this._addToWorkspaceEnabledExtensions(identifier);556}557558private _disableExtensionInWorkspace(identifier: IExtensionIdentifier): void {559this._addToWorkspaceDisabledExtensions(identifier);560this._removeFromWorkspaceEnabledExtensions(identifier);561}562563private _addToWorkspaceDisabledExtensions(identifier: IExtensionIdentifier): Promise<boolean> {564if (!this.hasWorkspace) {565return Promise.resolve(false);566}567const disabledExtensions = this._getWorkspaceDisabledExtensions();568if (disabledExtensions.every(e => !areSameExtensions(e, identifier))) {569disabledExtensions.push(identifier);570this._setDisabledExtensions(disabledExtensions);571return Promise.resolve(true);572}573return Promise.resolve(false);574}575576private async _removeFromWorkspaceDisabledExtensions(identifier: IExtensionIdentifier): Promise<boolean> {577if (!this.hasWorkspace) {578return false;579}580const disabledExtensions = this._getWorkspaceDisabledExtensions();581for (let index = 0; index < disabledExtensions.length; index++) {582const disabledExtension = disabledExtensions[index];583if (areSameExtensions(disabledExtension, identifier)) {584disabledExtensions.splice(index, 1);585this._setDisabledExtensions(disabledExtensions);586return true;587}588}589return false;590}591592private _addToWorkspaceEnabledExtensions(identifier: IExtensionIdentifier): boolean {593if (!this.hasWorkspace) {594return false;595}596const enabledExtensions = this._getWorkspaceEnabledExtensions();597if (enabledExtensions.every(e => !areSameExtensions(e, identifier))) {598enabledExtensions.push(identifier);599this._setEnabledExtensions(enabledExtensions);600return true;601}602return false;603}604605private _removeFromWorkspaceEnabledExtensions(identifier: IExtensionIdentifier): boolean {606if (!this.hasWorkspace) {607return false;608}609const enabledExtensions = this._getWorkspaceEnabledExtensions();610for (let index = 0; index < enabledExtensions.length; index++) {611const disabledExtension = enabledExtensions[index];612if (areSameExtensions(disabledExtension, identifier)) {613enabledExtensions.splice(index, 1);614this._setEnabledExtensions(enabledExtensions);615return true;616}617}618return false;619}620621protected _getWorkspaceEnabledExtensions(): IExtensionIdentifier[] {622return this._getExtensions(ENABLED_EXTENSIONS_STORAGE_PATH);623}624625private _setEnabledExtensions(enabledExtensions: IExtensionIdentifier[]): void {626this._setExtensions(ENABLED_EXTENSIONS_STORAGE_PATH, enabledExtensions);627}628629protected _getWorkspaceDisabledExtensions(): IExtensionIdentifier[] {630return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH);631}632633private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[]): void {634this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions);635}636637private _getExtensions(storageId: string): IExtensionIdentifier[] {638if (!this.hasWorkspace) {639return [];640}641return this.storageManager.get(storageId, StorageScope.WORKSPACE);642}643644private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void {645this.storageManager.set(storageId, extensions, StorageScope.WORKSPACE);646}647648private async _onDidChangeGloballyDisabledExtensions(extensionIdentifiers: ReadonlyArray<IExtensionIdentifier>, source?: string): Promise<void> {649if (source !== SOURCE) {650await this.extensionsManager.whenInitialized();651const extensions = this.extensionsManager.extensions.filter(installedExtension => extensionIdentifiers.some(identifier => areSameExtensions(identifier, installedExtension.identifier)));652this._onEnablementChanged.fire(extensions);653}654}655656private _onDidChangeExtensions(added: ReadonlyArray<IExtension>, removed: ReadonlyArray<IExtension>, isProfileSwitch: boolean): void {657const changedExtensions: IExtension[] = added.filter(e => !this.isEnabledEnablementState(this.getEnablementState(e)));658const existingDisabledExtensions = this.extensionsDisabledExtensions;659this.extensionsDisabledExtensions = this.extensionsManager.extensions.filter(extension => {660const enablementState = this.getEnablementState(extension);661return enablementState === EnablementState.DisabledByExtensionDependency || enablementState === EnablementState.DisabledByAllowlist || enablementState === EnablementState.DisabledByMalicious;662});663for (const extension of existingDisabledExtensions) {664if (this.extensionsDisabledExtensions.every(e => !areSameExtensions(e.identifier, extension.identifier))) {665changedExtensions.push(extension);666}667}668for (const extension of this.extensionsDisabledExtensions) {669if (existingDisabledExtensions.every(e => !areSameExtensions(e.identifier, extension.identifier))) {670changedExtensions.push(extension);671}672}673if (changedExtensions.length) {674this._onEnablementChanged.fire(changedExtensions);675}676if (!isProfileSwitch) {677removed.forEach(({ identifier }) => this._reset(identifier));678}679}680681public async updateExtensionsEnablementsWhenWorkspaceTrustChanges(): Promise<void> {682await this.extensionsManager.whenInitialized();683684const computeEnablementStates = (workspaceType: WorkspaceType): [IExtension, EnablementState][] => {685const extensionsEnablements = new Map<IExtension, EnablementState>();686return this.extensionsManager.extensions.map(extension => [extension, this._computeEnablementState(extension, this.extensionsManager.extensions, workspaceType, extensionsEnablements)]);687};688689const workspaceType = this.getWorkspaceType();690const enablementStatesWithTrustedWorkspace = computeEnablementStates({ ...workspaceType, trusted: true });691const enablementStatesWithUntrustedWorkspace = computeEnablementStates({ ...workspaceType, trusted: false });692const enablementChangedExtensionsBecauseOfTrust = enablementStatesWithTrustedWorkspace.filter(([, enablementState], index) => enablementState !== enablementStatesWithUntrustedWorkspace[index][1]).map(([extension]) => extension);693694if (enablementChangedExtensionsBecauseOfTrust.length) {695this._onEnablementChanged.fire(enablementChangedExtensionsBecauseOfTrust);696}697}698699private getWorkspaceType(): WorkspaceType {700return { trusted: this.workspaceTrustManagementService.isWorkspaceTrusted(), virtual: isVirtualWorkspace(this.contextService.getWorkspace()) };701}702703private _reset(extension: IExtensionIdentifier) {704this._removeFromWorkspaceDisabledExtensions(extension);705this._removeFromWorkspaceEnabledExtensions(extension);706this.globalExtensionEnablementService.enableExtension(extension);707}708709private loopCheckForMaliciousExtensions(): void {710this.checkForMaliciousExtensions()711.then(() => this.delayer.trigger(() => { }, 1000 * 60 * 5)) // every five minutes712.then(() => this.loopCheckForMaliciousExtensions());713}714715private async checkForMaliciousExtensions(): Promise<void> {716try {717const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();718const changed = this.storeMaliciousExtensions(extensionsControlManifest.malicious.map(({ extensionOrPublisher }) => extensionOrPublisher));719if (changed) {720this._onDidChangeExtensions([], [], false);721}722} catch (err) {723this.logService.error(err);724}725}726727private getMaliciousExtensions(): ReadonlyArray<IExtensionIdentifier | string> {728return this.storageService.getObject('extensionsEnablement/malicious', StorageScope.APPLICATION, []);729}730731private storeMaliciousExtensions(extensions: ReadonlyArray<IExtensionIdentifier | string>): boolean {732const existing = this.getMaliciousExtensions();733if (equals(existing, extensions, (a, b) => !isString(a) && !isString(b) ? areSameExtensions(a, b) : a === b)) {734return false;735}736this.storageService.store('extensionsEnablement/malicious', JSON.stringify(extensions), StorageScope.APPLICATION, StorageTarget.MACHINE);737return true;738}739}740741class ExtensionsManager extends Disposable {742743private _extensions: IExtension[] = [];744get extensions(): readonly IExtension[] { return this._extensions; }745746private _onDidChangeExtensions = this._register(new Emitter<{ added: readonly IExtension[]; removed: readonly IExtension[]; readonly isProfileSwitch: boolean }>());747readonly onDidChangeExtensions = this._onDidChangeExtensions.event;748749private readonly initializePromise;750private disposed: boolean = false;751752constructor(753@IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService,754@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,755@ILogService private readonly logService: ILogService756) {757super();758this._register(toDisposable(() => this.disposed = true));759this.initializePromise = this.initialize();760}761762whenInitialized(): Promise<void> {763return this.initializePromise;764}765766private async initialize(): Promise<void> {767try {768this._extensions = [769...await this.extensionManagementService.getInstalled(),770...await this.extensionManagementService.getInstalledWorkspaceExtensions(true)771];772if (this.disposed) {773return;774}775this._onDidChangeExtensions.fire({ added: this.extensions, removed: [], isProfileSwitch: false });776} catch (error) {777this.logService.error(error);778}779this._register(this.extensionManagementService.onDidInstallExtensions(e =>780this.updateExtensions(e.reduce<IExtension[]>((result, { local, operation }) => {781if (local && operation !== InstallOperation.Migrate) { result.push(local); } return result;782}, []), [], undefined, false)));783this._register(Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error))(e => this.updateExtensions([], [e.identifier], e.server, false)));784this._register(this.extensionManagementService.onDidChangeProfile(({ added, removed, server }) => {785this.updateExtensions(added, removed.map(({ identifier }) => identifier), server, true);786}));787}788789private updateExtensions(added: IExtension[], identifiers: IExtensionIdentifier[], server: IExtensionManagementServer | undefined, isProfileSwitch: boolean): void {790if (added.length) {791for (const extension of added) {792const extensionServer = this.extensionManagementServerService.getExtensionManagementServer(extension);793const index = this._extensions.findIndex(e => areSameExtensions(e.identifier, extension.identifier) && this.extensionManagementServerService.getExtensionManagementServer(e) === extensionServer);794if (index !== -1) {795this._extensions.splice(index, 1);796}797}798this._extensions.push(...added);799}800const removed: IExtension[] = [];801for (const identifier of identifiers) {802const index = this._extensions.findIndex(e => areSameExtensions(e.identifier, identifier) && this.extensionManagementServerService.getExtensionManagementServer(e) === server);803if (index !== -1) {804removed.push(...this._extensions.splice(index, 1));805}806}807if (added.length || removed.length) {808this._onDidChangeExtensions.fire({ added, removed, isProfileSwitch });809}810}811}812813registerSingleton(IWorkbenchExtensionEnablementService, ExtensionEnablementService, InstantiationType.Delayed);814815816