Path: blob/main/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts
5272 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';33import { IProductService } from '../../../../platform/product/common/productService.js';34import { isWeb } from '../../../../base/common/platform.js';3536const SOURCE = 'IWorkbenchExtensionEnablementService';3738type WorkspaceType = { readonly virtual: boolean; readonly trusted: boolean };3940const EXTENSION_UNIFICATION_SETTING = 'chat.extensionUnification.enabled';4142export class ExtensionEnablementService extends Disposable implements IWorkbenchExtensionEnablementService {4344declare readonly _serviceBrand: undefined;4546private readonly _onEnablementChanged = this._register(new Emitter<readonly IExtension[]>());47public readonly onEnablementChanged: Event<readonly IExtension[]> = this._onEnablementChanged.event;4849protected readonly extensionsManager: ExtensionsManager;50private readonly storageManager: StorageManager;51private extensionsDisabledExtensions: IExtension[] = [];52private readonly delayer = this._register(new Delayer<void>(0));5354// Extension unification55private readonly _completionsExtensionId: string | undefined;56private readonly _chatExtensionId: string | undefined;57private _extensionUnificationEnabled: boolean;5859constructor(60@IStorageService private readonly storageService: IStorageService,61@IGlobalExtensionEnablementService protected readonly globalExtensionEnablementService: IGlobalExtensionEnablementService,62@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,63@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,64@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,65@IConfigurationService private readonly configurationService: IConfigurationService,66@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,67@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,68@IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService,69@ILifecycleService private readonly lifecycleService: ILifecycleService,70@INotificationService private readonly notificationService: INotificationService,71@IHostService hostService: IHostService,72@IExtensionBisectService private readonly extensionBisectService: IExtensionBisectService,73@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,74@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,75@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,76@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,77@IInstantiationService instantiationService: IInstantiationService,78@ILogService private readonly logService: ILogService,79@IProductService productService: IProductService80) {81super();82this.storageManager = this._register(new StorageManager(storageService));8384const uninstallDisposable = this._register(Event.filter(extensionManagementService.onDidUninstallExtension, e => !e.error)(({ identifier }) => this._reset(identifier)));85let isDisposed = false;86this._register(toDisposable(() => isDisposed = true));87this.extensionsManager = this._register(instantiationService.createInstance(ExtensionsManager));88this.extensionsManager.whenInitialized().then(() => {89if (!isDisposed) {90uninstallDisposable.dispose();91this._onDidChangeExtensions([], [], false);92this._register(this.extensionsManager.onDidChangeExtensions(({ added, removed, isProfileSwitch }) => this._onDidChangeExtensions(added, removed, isProfileSwitch)));93this.loopCheckForMaliciousExtensions();94}95});9697this._register(this.globalExtensionEnablementService.onDidChangeEnablement(({ extensions, source }) => this._onDidChangeGloballyDisabledExtensions(extensions, source)));98this._register(allowedExtensionsService.onDidChangeAllowedExtensionsConfigValue(() => this._onDidChangeExtensions([], [], false)));99100// Extension unification101this._completionsExtensionId = productService.defaultChatAgent?.extensionId.toLowerCase();102this._chatExtensionId = productService.defaultChatAgent?.chatExtensionId.toLowerCase();103const unificationExtensions = [this._completionsExtensionId, this._chatExtensionId].filter(id => !!id);104105// Disabling extension unification should immediately disable the unified extension flow106// Enabling extension unification will only take effect after restart107// Extension Unification is disabled in web when there is no remote authority108if (isWeb && this.environmentService.remoteAuthority === undefined) {109this._extensionUnificationEnabled = false;110} else {111this._extensionUnificationEnabled = this.configurationService.getValue<boolean>(EXTENSION_UNIFICATION_SETTING);112}113this._register(this.configurationService.onDidChangeConfiguration(e => {114if (e.affectsConfiguration(EXTENSION_UNIFICATION_SETTING)) {115const extensionUnificationEnabled = this.configurationService.getValue<boolean>(EXTENSION_UNIFICATION_SETTING);116if (!extensionUnificationEnabled) {117this._extensionUnificationEnabled = false;118this._onEnablementChanged.fire(this.extensionsManager.extensions.filter(ext => unificationExtensions.includes(ext.identifier.id.toLowerCase())));119}120}121}));122123// delay notification for extensions disabled until workbench restored124if (this.allUserExtensionsDisabled) {125this.lifecycleService.when(LifecyclePhase.Eventually).then(() => {126this.notificationService.prompt(Severity.Info, localize('extensionsDisabled', "All installed extensions are temporarily disabled."), [{127label: localize('Reload', "Reload and Enable Extensions"),128run: () => hostService.reload({ disableExtensions: false })129}], {130sticky: true,131priority: NotificationPriority.URGENT132});133});134}135}136137private get hasWorkspace(): boolean {138return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY;139}140141private get allUserExtensionsDisabled(): boolean {142return this.environmentService.disableExtensions === true;143}144145getEnablementState(extension: IExtension): EnablementState {146return this._computeEnablementState(extension, this.extensionsManager.extensions, this.getWorkspaceType());147}148149getEnablementStates(extensions: IExtension[], workspaceTypeOverrides: Partial<WorkspaceType> = {}): EnablementState[] {150const extensionsEnablements = new Map<IExtension, EnablementState>();151const workspaceType = { ...this.getWorkspaceType(), ...workspaceTypeOverrides };152return extensions.map(extension => this._computeEnablementState(extension, extensions, workspaceType, extensionsEnablements));153}154155getDependenciesEnablementStates(extension: IExtension): [IExtension, EnablementState][] {156return getExtensionDependencies(this.extensionsManager.extensions, extension).map(e => [e, this.getEnablementState(e)]);157}158159canChangeEnablement(extension: IExtension): boolean {160try {161this.throwErrorIfCannotChangeEnablement(extension);162return true;163} catch (error) {164return false;165}166}167168canChangeWorkspaceEnablement(extension: IExtension): boolean {169if (!this.canChangeEnablement(extension)) {170return false;171}172173try {174this.throwErrorIfCannotChangeWorkspaceEnablement(extension);175return true;176} catch (error) {177return false;178}179}180181private throwErrorIfCannotChangeEnablement(extension: IExtension, donotCheckDependencies?: boolean): void {182if (isLanguagePackExtension(extension.manifest)) {183throw 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));184}185186if (this.userDataSyncEnablementService.isEnabled() && this.userDataSyncAccountService.account &&187isAuthenticationProviderExtension(extension.manifest) && extension.manifest.contributes!.authentication!.some(a => a.id === this.userDataSyncAccountService.account!.authenticationProviderId)) {188throw new Error(localize('cannot disable auth extension', "Cannot change enablement {0} extension because Settings Sync depends on it.", extension.manifest.displayName || extension.identifier.id));189}190191if (this._isEnabledInEnv(extension)) {192throw 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));193}194195this.throwErrorIfEnablementStateCannotBeChanged(extension, this.getEnablementState(extension), donotCheckDependencies);196}197198private throwErrorIfEnablementStateCannotBeChanged(extension: IExtension, enablementStateOfExtension: EnablementState, donotCheckDependencies?: boolean): void {199switch (enablementStateOfExtension) {200case EnablementState.DisabledByEnvironment:201throw 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));202case EnablementState.DisabledByMalicious:203throw new Error(localize('cannot change enablement malicious', "Cannot change enablement of {0} extension because it is malicious", extension.manifest.displayName || extension.identifier.id));204case EnablementState.DisabledByVirtualWorkspace:205throw 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));206case EnablementState.DisabledByExtensionKind:207throw 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));208case EnablementState.DisabledByAllowlist:209throw new Error(localize('cannot change disallowed extension enablement', "Cannot change enablement of {0} extension because it is disallowed", extension.manifest.displayName || extension.identifier.id));210case EnablementState.DisabledByInvalidExtension:211throw 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));212case EnablementState.DisabledByExtensionDependency:213if (donotCheckDependencies) {214break;215}216// Can be changed only when all its dependencies enablements can be changed217for (const dependency of getExtensionDependencies(this.extensionsManager.extensions, extension)) {218if (this.isEnabled(dependency)) {219continue;220}221throw 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));222}223}224}225226private throwErrorIfCannotChangeWorkspaceEnablement(extension: IExtension): void {227if (!this.hasWorkspace) {228throw new Error(localize('noWorkspace', "No workspace."));229}230if (isAuthenticationProviderExtension(extension.manifest)) {231throw 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));232}233}234235async setEnablement(extensions: IExtension[], newState: EnablementState): Promise<boolean[]> {236await this.extensionsManager.whenInitialized();237238if (newState === EnablementState.EnabledGlobally || newState === EnablementState.EnabledWorkspace) {239extensions.push(...this.getExtensionsToEnableRecursively(extensions, this.extensionsManager.extensions, newState, { dependencies: true, pack: true }));240}241242const workspace = newState === EnablementState.DisabledWorkspace || newState === EnablementState.EnabledWorkspace;243for (const extension of extensions) {244if (workspace) {245this.throwErrorIfCannotChangeWorkspaceEnablement(extension);246} else {247this.throwErrorIfCannotChangeEnablement(extension);248}249}250251const result: boolean[] = [];252for (const extension of extensions) {253const enablementState = this.getEnablementState(extension);254if (enablementState === EnablementState.DisabledByTrustRequirement255/* All its disabled dependencies are disabled by Trust Requirement */256|| (enablementState === EnablementState.DisabledByExtensionDependency && this.getDependenciesEnablementStates(extension).every(([, e]) => this.isEnabledEnablementState(e) || e === EnablementState.DisabledByTrustRequirement))257) {258const trustState = await this.workspaceTrustRequestService.requestWorkspaceTrust();259result.push(trustState ?? false);260} else {261result.push(await this._setUserEnablementState(extension, newState));262}263}264265const changedExtensions = extensions.filter((e, index) => result[index]);266if (changedExtensions.length) {267this._onEnablementChanged.fire(changedExtensions);268}269return result;270}271272private getExtensionsToEnableRecursively(extensions: IExtension[], allExtensions: ReadonlyArray<IExtension>, enablementState: EnablementState, options: { dependencies: boolean; pack: boolean }, checked: IExtension[] = []): IExtension[] {273if (!options.dependencies && !options.pack) {274return [];275}276277const toCheck = extensions.filter(e => checked.indexOf(e) === -1);278if (!toCheck.length) {279return [];280}281282for (const extension of toCheck) {283checked.push(extension);284}285286const extensionsToEnable: IExtension[] = [];287for (const extension of allExtensions) {288// Extension is already checked289if (checked.some(e => areSameExtensions(e.identifier, extension.identifier))) {290continue;291}292293const enablementStateOfExtension = this.getEnablementState(extension);294// Extension is enabled295if (this.isEnabledEnablementState(enablementStateOfExtension)) {296continue;297}298299// Skip if dependency extension is disabled by extension kind300if (enablementStateOfExtension === EnablementState.DisabledByExtensionKind) {301continue;302}303304// Check if the extension is a dependency or in extension pack305if (extensions.some(e =>306(options.dependencies && e.manifest.extensionDependencies?.some(id => areSameExtensions({ id }, extension.identifier)))307|| (options.pack && e.manifest.extensionPack?.some(id => areSameExtensions({ id }, extension.identifier))))) {308309const index = extensionsToEnable.findIndex(e => areSameExtensions(e.identifier, extension.identifier));310311// Extension is not added to the disablement list so add it312if (index === -1) {313extensionsToEnable.push(extension);314}315316// Extension is there already in the disablement list.317else {318try {319// Replace only if the enablement state can be changed320this.throwErrorIfEnablementStateCannotBeChanged(extension, enablementStateOfExtension, true);321extensionsToEnable.splice(index, 1, extension);322} catch (error) { /*Do not add*/ }323}324}325}326327if (extensionsToEnable.length) {328extensionsToEnable.push(...this.getExtensionsToEnableRecursively(extensionsToEnable, allExtensions, enablementState, options, checked));329}330331return extensionsToEnable;332}333334private _setUserEnablementState(extension: IExtension, newState: EnablementState): Promise<boolean> {335336const currentState = this._getUserEnablementState(extension.identifier);337338if (currentState === newState) {339return Promise.resolve(false);340}341342switch (newState) {343case EnablementState.EnabledGlobally:344this._enableExtension(extension.identifier);345break;346case EnablementState.DisabledGlobally:347this._disableExtension(extension.identifier);348break;349case EnablementState.EnabledWorkspace:350this._enableExtensionInWorkspace(extension.identifier);351break;352case EnablementState.DisabledWorkspace:353this._disableExtensionInWorkspace(extension.identifier);354break;355}356357return Promise.resolve(true);358}359360isEnabled(extension: IExtension): boolean {361const enablementState = this.getEnablementState(extension);362return this.isEnabledEnablementState(enablementState);363}364365isEnabledEnablementState(enablementState: EnablementState): boolean {366return enablementState === EnablementState.EnabledByEnvironment || enablementState === EnablementState.EnabledWorkspace || enablementState === EnablementState.EnabledGlobally;367}368369isDisabledGlobally(extension: IExtension): boolean {370return this._isDisabledGlobally(extension.identifier);371}372373private _computeEnablementState(extension: IExtension, extensions: ReadonlyArray<IExtension>, workspaceType: WorkspaceType, computedEnablementStates?: Map<IExtension, EnablementState>): EnablementState {374computedEnablementStates = computedEnablementStates ?? new Map<IExtension, EnablementState>();375let enablementState = computedEnablementStates.get(extension);376if (enablementState !== undefined) {377return enablementState;378}379380enablementState = this._getUserEnablementState(extension.identifier);381const isEnabled = this.isEnabledEnablementState(enablementState);382383if (isMalicious(extension.identifier, this.getMaliciousExtensions().map(e => ({ extensionOrPublisher: e })))) {384enablementState = EnablementState.DisabledByMalicious;385}386387else if (isEnabled && extension.type === ExtensionType.User && this.allowedExtensionsService.isAllowed(extension) !== true) {388enablementState = EnablementState.DisabledByAllowlist;389}390391else if (isEnabled && !extension.isValid) {392enablementState = EnablementState.DisabledByInvalidExtension;393}394395else if (this.extensionBisectService.isDisabledByBisect(extension)) {396enablementState = EnablementState.DisabledByEnvironment;397}398399else if (this._isDisabledInEnv(extension)) {400enablementState = EnablementState.DisabledByEnvironment;401}402403else if (this._isDisabledByVirtualWorkspace(extension, workspaceType)) {404enablementState = EnablementState.DisabledByVirtualWorkspace;405}406407else if (isEnabled && this._isDisabledByWorkspaceTrust(extension, workspaceType)) {408enablementState = EnablementState.DisabledByTrustRequirement;409}410411else if (this._isDisabledByExtensionKind(extension)) {412enablementState = EnablementState.DisabledByExtensionKind;413}414415else if (isEnabled && this._isDisabledByExtensionDependency(extension, extensions, workspaceType, computedEnablementStates)) {416enablementState = EnablementState.DisabledByExtensionDependency;417}418419else if (this._isDisabledByUnification(extension.identifier)) {420enablementState = EnablementState.DisabledByUnification;421}422423else if (!isEnabled && this._isEnabledInEnv(extension)) {424enablementState = EnablementState.EnabledByEnvironment;425}426427computedEnablementStates.set(extension, enablementState);428return enablementState;429}430431private _isDisabledInEnv(extension: IExtension): boolean {432if (this.allUserExtensionsDisabled) {433return !extension.isBuiltin && !isResolverExtension(extension.manifest, this.environmentService.remoteAuthority);434}435436const disabledExtensions = this.environmentService.disableExtensions;437if (Array.isArray(disabledExtensions)) {438return disabledExtensions.some(id => areSameExtensions({ id }, extension.identifier));439}440441// Check if this is the better merge extension which was migrated to a built-in extension442if (areSameExtensions({ id: BetterMergeId.value }, extension.identifier)) {443return true;444}445446return false;447}448449private _isEnabledInEnv(extension: IExtension): boolean {450const enabledExtensions = this.environmentService.enableExtensions;451if (Array.isArray(enabledExtensions)) {452return enabledExtensions.some(id => areSameExtensions({ id }, extension.identifier));453}454return false;455}456457private _isDisabledByVirtualWorkspace(extension: IExtension, workspaceType: WorkspaceType): boolean {458// Not a virtual workspace459if (!workspaceType.virtual) {460return false;461}462463// Supports virtual workspace464if (this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType(extension.manifest) !== false) {465return false;466}467468// Web extension from web extension management server469if (this.extensionManagementServerService.getExtensionManagementServer(extension) === this.extensionManagementServerService.webExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWeb(extension.manifest)) {470return false;471}472473return true;474}475476private _isDisabledByExtensionKind(extension: IExtension): boolean {477if (this.extensionManagementServerService.remoteExtensionManagementServer || this.extensionManagementServerService.webExtensionManagementServer) {478const installLocation = this.extensionManagementServerService.getExtensionInstallLocation(extension);479for (const extensionKind of this.extensionManifestPropertiesService.getExtensionKind(extension.manifest)) {480if (extensionKind === 'ui') {481if (installLocation === ExtensionInstallLocation.Local) {482return false;483}484}485if (extensionKind === 'workspace') {486if (installLocation === ExtensionInstallLocation.Remote) {487return false;488}489}490if (extensionKind === 'web') {491if (this.extensionManagementServerService.webExtensionManagementServer /* web */) {492if (installLocation === ExtensionInstallLocation.Web || installLocation === ExtensionInstallLocation.Remote) {493return false;494}495} else if (installLocation === ExtensionInstallLocation.Local) {496const enableLocalWebWorker = this.configurationService.getValue<WebWorkerExtHostConfigValue>(webWorkerExtHostConfig);497if (enableLocalWebWorker === true || enableLocalWebWorker === 'auto') {498// Web extensions are enabled on all configurations499return false;500}501}502}503}504return true;505}506return false;507}508509private _isDisabledByWorkspaceTrust(extension: IExtension, workspaceType: WorkspaceType): boolean {510if (workspaceType.trusted) {511return false;512}513514if (this.contextService.isInsideWorkspace(extension.location)) {515return true;516}517518return this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.manifest) === false;519}520521private _isDisabledByExtensionDependency(extension: IExtension, extensions: ReadonlyArray<IExtension>, workspaceType: WorkspaceType, computedEnablementStates: Map<IExtension, EnablementState>): boolean {522523if (!extension.manifest.extensionDependencies) {524return false;525}526527// Find dependency that is from the same server or does not exports any API528const dependencyExtensions = extensions.filter(e =>529extension.manifest.extensionDependencies?.some(id => areSameExtensions(e.identifier, { id })530&& (this.extensionManagementServerService.getExtensionManagementServer(e) === this.extensionManagementServerService.getExtensionManagementServer(extension) || ((e.manifest.main || e.manifest.browser) && e.manifest.api === 'none'))));531532if (!dependencyExtensions.length) {533return false;534}535536const hasEnablementState = computedEnablementStates.has(extension);537if (!hasEnablementState) {538// Placeholder to handle cyclic deps539computedEnablementStates.set(extension, EnablementState.EnabledGlobally);540}541try {542for (const dependencyExtension of dependencyExtensions) {543const enablementState = this._computeEnablementState(dependencyExtension, extensions, workspaceType, computedEnablementStates);544if (!this.isEnabledEnablementState(enablementState) && enablementState !== EnablementState.DisabledByExtensionKind) {545return true;546}547}548} finally {549if (!hasEnablementState) {550// remove the placeholder551computedEnablementStates.delete(extension);552}553}554555return false;556}557558private _getUserEnablementState(identifier: IExtensionIdentifier): EnablementState {559if (this.hasWorkspace) {560if (this._getWorkspaceEnabledExtensions().filter(e => areSameExtensions(e, identifier))[0]) {561return EnablementState.EnabledWorkspace;562}563564if (this._getWorkspaceDisabledExtensions().filter(e => areSameExtensions(e, identifier))[0]) {565return EnablementState.DisabledWorkspace;566}567}568if (this._isDisabledGlobally(identifier)) {569return EnablementState.DisabledGlobally;570}571return EnablementState.EnabledGlobally;572}573574private _isDisabledGlobally(identifier: IExtensionIdentifier): boolean {575return this.globalExtensionEnablementService.getDisabledExtensions().some(e => areSameExtensions(e, identifier));576}577578private _isDisabledByUnification(identifier: IExtensionIdentifier): boolean {579return this._extensionUnificationEnabled && identifier.id.toLowerCase() === this._completionsExtensionId;580}581582private _enableExtension(identifier: IExtensionIdentifier): Promise<boolean> {583this._removeFromWorkspaceDisabledExtensions(identifier);584this._removeFromWorkspaceEnabledExtensions(identifier);585return this.globalExtensionEnablementService.enableExtension(identifier, SOURCE);586}587588private _disableExtension(identifier: IExtensionIdentifier): Promise<boolean> {589this._removeFromWorkspaceDisabledExtensions(identifier);590this._removeFromWorkspaceEnabledExtensions(identifier);591return this.globalExtensionEnablementService.disableExtension(identifier, SOURCE);592}593594private _enableExtensionInWorkspace(identifier: IExtensionIdentifier): void {595this._removeFromWorkspaceDisabledExtensions(identifier);596this._addToWorkspaceEnabledExtensions(identifier);597}598599private _disableExtensionInWorkspace(identifier: IExtensionIdentifier): void {600this._addToWorkspaceDisabledExtensions(identifier);601this._removeFromWorkspaceEnabledExtensions(identifier);602}603604private _addToWorkspaceDisabledExtensions(identifier: IExtensionIdentifier): Promise<boolean> {605if (!this.hasWorkspace) {606return Promise.resolve(false);607}608const disabledExtensions = this._getWorkspaceDisabledExtensions();609if (disabledExtensions.every(e => !areSameExtensions(e, identifier))) {610disabledExtensions.push(identifier);611this._setDisabledExtensions(disabledExtensions);612return Promise.resolve(true);613}614return Promise.resolve(false);615}616617private async _removeFromWorkspaceDisabledExtensions(identifier: IExtensionIdentifier): Promise<boolean> {618if (!this.hasWorkspace) {619return false;620}621const disabledExtensions = this._getWorkspaceDisabledExtensions();622for (let index = 0; index < disabledExtensions.length; index++) {623const disabledExtension = disabledExtensions[index];624if (areSameExtensions(disabledExtension, identifier)) {625disabledExtensions.splice(index, 1);626this._setDisabledExtensions(disabledExtensions);627return true;628}629}630return false;631}632633private _addToWorkspaceEnabledExtensions(identifier: IExtensionIdentifier): boolean {634if (!this.hasWorkspace) {635return false;636}637const enabledExtensions = this._getWorkspaceEnabledExtensions();638if (enabledExtensions.every(e => !areSameExtensions(e, identifier))) {639enabledExtensions.push(identifier);640this._setEnabledExtensions(enabledExtensions);641return true;642}643return false;644}645646private _removeFromWorkspaceEnabledExtensions(identifier: IExtensionIdentifier): boolean {647if (!this.hasWorkspace) {648return false;649}650const enabledExtensions = this._getWorkspaceEnabledExtensions();651for (let index = 0; index < enabledExtensions.length; index++) {652const disabledExtension = enabledExtensions[index];653if (areSameExtensions(disabledExtension, identifier)) {654enabledExtensions.splice(index, 1);655this._setEnabledExtensions(enabledExtensions);656return true;657}658}659return false;660}661662protected _getWorkspaceEnabledExtensions(): IExtensionIdentifier[] {663return this._getExtensions(ENABLED_EXTENSIONS_STORAGE_PATH);664}665666private _setEnabledExtensions(enabledExtensions: IExtensionIdentifier[]): void {667this._setExtensions(ENABLED_EXTENSIONS_STORAGE_PATH, enabledExtensions);668}669670protected _getWorkspaceDisabledExtensions(): IExtensionIdentifier[] {671return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH);672}673674private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[]): void {675this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions);676}677678private _getExtensions(storageId: string): IExtensionIdentifier[] {679if (!this.hasWorkspace) {680return [];681}682return this.storageManager.get(storageId, StorageScope.WORKSPACE);683}684685private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void {686this.storageManager.set(storageId, extensions, StorageScope.WORKSPACE);687}688689private async _onDidChangeGloballyDisabledExtensions(extensionIdentifiers: ReadonlyArray<IExtensionIdentifier>, source?: string): Promise<void> {690if (source !== SOURCE) {691await this.extensionsManager.whenInitialized();692const extensions = this.extensionsManager.extensions.filter(installedExtension => extensionIdentifiers.some(identifier => areSameExtensions(identifier, installedExtension.identifier)));693this._onEnablementChanged.fire(extensions);694}695}696697private _onDidChangeExtensions(added: ReadonlyArray<IExtension>, removed: ReadonlyArray<IExtension>, isProfileSwitch: boolean): void {698const changedExtensions: IExtension[] = added.filter(e => !this.isEnabledEnablementState(this.getEnablementState(e)));699const existingDisabledExtensions = this.extensionsDisabledExtensions;700this.extensionsDisabledExtensions = this.extensionsManager.extensions.filter(extension => {701const enablementState = this.getEnablementState(extension);702return enablementState === EnablementState.DisabledByExtensionDependency || enablementState === EnablementState.DisabledByAllowlist || enablementState === EnablementState.DisabledByMalicious;703});704for (const extension of existingDisabledExtensions) {705if (this.extensionsDisabledExtensions.every(e => !areSameExtensions(e.identifier, extension.identifier))) {706changedExtensions.push(extension);707}708}709for (const extension of this.extensionsDisabledExtensions) {710if (existingDisabledExtensions.every(e => !areSameExtensions(e.identifier, extension.identifier))) {711changedExtensions.push(extension);712}713}714if (changedExtensions.length) {715this._onEnablementChanged.fire(changedExtensions);716}717if (!isProfileSwitch) {718removed.forEach(({ identifier }) => this._reset(identifier));719}720}721722public async updateExtensionsEnablementsWhenWorkspaceTrustChanges(): Promise<void> {723await this.extensionsManager.whenInitialized();724725const computeEnablementStates = (workspaceType: WorkspaceType): [IExtension, EnablementState][] => {726const extensionsEnablements = new Map<IExtension, EnablementState>();727return this.extensionsManager.extensions.map(extension => [extension, this._computeEnablementState(extension, this.extensionsManager.extensions, workspaceType, extensionsEnablements)]);728};729730const workspaceType = this.getWorkspaceType();731const enablementStatesWithTrustedWorkspace = computeEnablementStates({ ...workspaceType, trusted: true });732const enablementStatesWithUntrustedWorkspace = computeEnablementStates({ ...workspaceType, trusted: false });733const enablementChangedExtensionsBecauseOfTrust = enablementStatesWithTrustedWorkspace.filter(([, enablementState], index) => enablementState !== enablementStatesWithUntrustedWorkspace[index][1]).map(([extension]) => extension);734735if (enablementChangedExtensionsBecauseOfTrust.length) {736this._onEnablementChanged.fire(enablementChangedExtensionsBecauseOfTrust);737}738}739740private getWorkspaceType(): WorkspaceType {741return { trusted: this.workspaceTrustManagementService.isWorkspaceTrusted(), virtual: isVirtualWorkspace(this.contextService.getWorkspace()) };742}743744private _reset(extension: IExtensionIdentifier) {745this._removeFromWorkspaceDisabledExtensions(extension);746this._removeFromWorkspaceEnabledExtensions(extension);747this.globalExtensionEnablementService.enableExtension(extension);748}749750private loopCheckForMaliciousExtensions(): void {751this.checkForMaliciousExtensions()752.then(() => this.delayer.trigger(() => { }, 1000 * 60 * 5)) // every five minutes753.then(() => this.loopCheckForMaliciousExtensions());754}755756private async checkForMaliciousExtensions(): Promise<void> {757try {758const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();759const changed = this.storeMaliciousExtensions(extensionsControlManifest.malicious.map(({ extensionOrPublisher }) => extensionOrPublisher));760if (changed) {761this._onDidChangeExtensions([], [], false);762}763} catch (err) {764this.logService.error(err);765}766}767768private getMaliciousExtensions(): ReadonlyArray<IExtensionIdentifier | string> {769return this.storageService.getObject('extensionsEnablement/malicious', StorageScope.APPLICATION, []);770}771772private storeMaliciousExtensions(extensions: ReadonlyArray<IExtensionIdentifier | string>): boolean {773const existing = this.getMaliciousExtensions();774if (equals(existing, extensions, (a, b) => !isString(a) && !isString(b) ? areSameExtensions(a, b) : a === b)) {775return false;776}777this.storageService.store('extensionsEnablement/malicious', JSON.stringify(extensions), StorageScope.APPLICATION, StorageTarget.MACHINE);778return true;779}780}781782class ExtensionsManager extends Disposable {783784private _extensions: IExtension[] = [];785get extensions(): readonly IExtension[] { return this._extensions; }786787private _onDidChangeExtensions = this._register(new Emitter<{ added: readonly IExtension[]; removed: readonly IExtension[]; readonly isProfileSwitch: boolean }>());788readonly onDidChangeExtensions = this._onDidChangeExtensions.event;789790private readonly initializePromise;791private disposed: boolean = false;792793constructor(794@IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService,795@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,796@ILogService private readonly logService: ILogService797) {798super();799this._register(toDisposable(() => this.disposed = true));800this.initializePromise = this.initialize();801}802803whenInitialized(): Promise<void> {804return this.initializePromise;805}806807private async initialize(): Promise<void> {808try {809this._extensions = [810...await this.extensionManagementService.getInstalled(),811...await this.extensionManagementService.getInstalledWorkspaceExtensions(true)812];813if (this.disposed) {814return;815}816this._onDidChangeExtensions.fire({ added: this.extensions, removed: [], isProfileSwitch: false });817} catch (error) {818this.logService.error(error);819}820this._register(this.extensionManagementService.onDidInstallExtensions(e =>821this.updateExtensions(e.reduce<IExtension[]>((result, { local, operation }) => {822if (local && operation !== InstallOperation.Migrate) { result.push(local); } return result;823}, []), [], undefined, false)));824this._register(Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error))(e => this.updateExtensions([], [e.identifier], e.server, false)));825this._register(this.extensionManagementService.onDidChangeProfile(({ added, removed, server }) => {826this.updateExtensions(added, removed.map(({ identifier }) => identifier), server, true);827}));828}829830private updateExtensions(added: IExtension[], identifiers: IExtensionIdentifier[], server: IExtensionManagementServer | undefined, isProfileSwitch: boolean): void {831if (added.length) {832for (const extension of added) {833const extensionServer = this.extensionManagementServerService.getExtensionManagementServer(extension);834const index = this._extensions.findIndex(e => areSameExtensions(e.identifier, extension.identifier) && this.extensionManagementServerService.getExtensionManagementServer(e) === extensionServer);835if (index !== -1) {836this._extensions.splice(index, 1);837}838}839this._extensions.push(...added);840}841const removed: IExtension[] = [];842for (const identifier of identifiers) {843const index = this._extensions.findIndex(e => areSameExtensions(e.identifier, identifier) && this.extensionManagementServerService.getExtensionManagementServer(e) === server);844if (index !== -1) {845removed.push(...this._extensions.splice(index, 1));846}847}848if (added.length || removed.length) {849this._onDidChangeExtensions.fire({ added, removed, isProfileSwitch });850}851}852}853854registerSingleton(IWorkbenchExtensionEnablementService, ExtensionEnablementService, InstantiationType.Delayed);855856857