Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -723,22 +723,14 @@ export abstract class AbstractExtensionManagementService extends CommontExtensio
this.logService.info('Successfully uninstalled extension from the profile', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString());
}
reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', { extensionData: getLocalExtensionTelemetryData(extension), error });
this._onDidUninstallExtension.fire({ identifier: extension.identifier, context: uninstallOptions.context, error: error?.code, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped });
this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: error?.code, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped });
};

const allTasks: IUninstallExtensionTask[] = [];
const processedTasks: IUninstallExtensionTask[] = [];
const alreadyRequestedUninstalls: Promise<any>[] = [];
const extensionsToRemove: ILocalExtension[] = [];

const installedExtensionsMap = new ResourceMap<ILocalExtension[]>();
const getInstalledExtensions = async (profileLocation: URI) => {
let installed = installedExtensionsMap.get(profileLocation);
if (!installed) {
installedExtensionsMap.set(profileLocation, installed = await this.getInstalled(ExtensionType.User, profileLocation));
}
return installed;
};

for (const { extension, options } of extensions) {
const uninstallOptions: UninstallExtensionTaskOptions = {
Expand All @@ -752,32 +744,14 @@ export abstract class AbstractExtensionManagementService extends CommontExtensio
} else {
allTasks.push(createUninstallExtensionTask(extension, uninstallOptions));
}

if (uninstallOptions.remove) {
extensionsToRemove.push(extension);
for (const profile of this.userDataProfilesService.profiles) {
if (this.uriIdentityService.extUri.isEqual(profile.extensionsResource, uninstallOptions.profileLocation)) {
continue;
}
const installed = await getInstalledExtensions(profile.extensionsResource);
const profileExtension = installed.find(e => areSameExtensions(e.identifier, extension.identifier));
if (profileExtension) {
const uninstallOptionsWithProfile = { ...uninstallOptions, profileLocation: profile.extensionsResource };
const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(profileExtension, uninstallOptionsWithProfile));
if (uninstallExtensionTask) {
this.logService.info('Extensions is already requested to uninstall', profileExtension.identifier.id);
alreadyRequestedUninstalls.push(uninstallExtensionTask.waitUntilTaskIsFinished());
} else {
allTasks.push(createUninstallExtensionTask(profileExtension, uninstallOptionsWithProfile));
}
}
}
}
}

try {
for (const task of allTasks.slice(0)) {
const installed = await getInstalledExtensions(task.options.profileLocation);
let installed = installedExtensionsMap.get(task.options.profileLocation);
if (!installed) {
installedExtensionsMap.set(task.options.profileLocation, installed = await this.getInstalled(ExtensionType.User, task.options.profileLocation));
}

if (task.options.donotIncludePack) {
this.logService.info('Uninstalling the extension without including packed extension', `${task.extension.identifier.id}@${task.extension.manifest.version}`);
Expand Down Expand Up @@ -825,10 +799,6 @@ export abstract class AbstractExtensionManagementService extends CommontExtensio
for (const task of allTasks) {
postUninstallExtension(task.extension, task.options);
}

if (extensionsToRemove.length) {
await this.joinAllSettled(extensionsToRemove.map(extension => this.removeExtension(extension)));
}
} catch (e) {
const error = toExtensionManagementError(e);
for (const task of allTasks) {
Expand Down Expand Up @@ -925,7 +895,6 @@ export abstract class AbstractExtensionManagementService extends CommontExtensio
protected abstract createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask;
protected abstract createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask;
protected abstract copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata?: Partial<Metadata>): Promise<ILocalExtension>;
protected abstract removeExtension(extension: ILocalExtension): Promise<void>;
}

export function toExtensionManagementError(error: Error, code?: ExtensionManagementErrorCode): ExtensionManagementError {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export const EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT = 'skipPublisherTrus
export const EXTENSION_INSTALL_SOURCE_CONTEXT = 'extensionInstallSource';
export const EXTENSION_INSTALL_DEP_PACK_CONTEXT = 'dependecyOrPackExtensionInstall';
export const EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT = 'clientTargetPlatform';
export const EXTENSION_UNINSTALL_MALICIOUS_CONTEXT = 'uninstallMaliciousExtension';

export const enum ExtensionInstallSource {
COMMAND = 'command',
Expand Down Expand Up @@ -416,7 +415,6 @@ export interface UninstallExtensionEvent {

export interface DidUninstallExtensionEvent {
readonly identifier: IExtensionIdentifier;
readonly context?: IStringDictionary<any>;
readonly error?: string;
readonly profileLocation: URI;
readonly applicationScoped?: boolean;
Expand Down Expand Up @@ -551,10 +549,6 @@ export type UninstallOptions = {
readonly donotCheckDependents?: boolean;
readonly versionOnly?: boolean;
readonly remove?: boolean;
/**
* Context passed through to DidUninstallExtensionEvent
*/
context?: IStringDictionary<any>;
};

export interface IExtensionManagementParticipant {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
return local;
}

protected removeExtension(extension: ILocalExtension): Promise<void> {
return this.extensionsScanner.deleteExtension(extension, 'remove');
}

protected copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial<Metadata>): Promise<ILocalExtension> {
return this.extensionsScanner.copyExtension(extension, fromProfileLocation, toProfileLocation, metadata);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer,
import { InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction, ToggleAutoUpdateForExtensionAction, ToggleAutoUpdatesForPublisherAction, TogglePreReleaseExtensionAction, InstallAnotherVersionAction, InstallAction } from './extensionsActions.js';
import { ExtensionsInput } from '../common/extensionsInput.js';
import { ExtensionEditor } from './extensionEditor.js';
import { StatusUpdater, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer, BuiltInExtensionsContext, SearchMarketplaceExtensionsContext, RecommendedExtensionsContext, DefaultViewsContext, ExtensionsSortByContext, SearchHasTextContext, ExtensionsSearchValueContext } from './extensionsViewlet.js';
import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer, BuiltInExtensionsContext, SearchMarketplaceExtensionsContext, RecommendedExtensionsContext, DefaultViewsContext, ExtensionsSortByContext, SearchHasTextContext, ExtensionsSearchValueContext } from './extensionsViewlet.js';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from '../../../../platform/configuration/common/configurationRegistry.js';
import * as jsonContributionRegistry from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js';
import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from '../common/extensionsFileTemplate.js';
Expand Down Expand Up @@ -1670,7 +1670,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
title: localize('download VSIX', "Download VSIX"),
menu: {
id: MenuId.ExtensionContext,
when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.not('extensionDisallowInstall'), ContextKeyExpr.has('isGalleryExtension')),
when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension')),
order: this.productService.quality === 'stable' ? 0 : 1
},
run: async (accessor: ServicesAccessor, extensionId: string) => {
Expand All @@ -1683,7 +1683,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
title: localize('download pre-release', "Download Pre-Release VSIX"),
menu: {
id: MenuId.ExtensionContext,
when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.not('extensionDisallowInstall'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.has('extensionHasPreReleaseVersion')),
when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.has('extensionHasPreReleaseVersion')),
order: this.productService.quality === 'stable' ? 1 : 0
},
run: async (accessor: ServicesAccessor, extensionId: string) => {
Expand Down Expand Up @@ -1997,6 +1997,7 @@ class TrustedPublishersInitializer implements IWorkbenchContribution {
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(MaliciousExtensionChecker, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1250,7 +1250,7 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n
cksOverlay.push(['galleryExtensionHasPreReleaseVersion', extension.gallery?.hasPreReleaseVersion]);
cksOverlay.push(['extensionHasPreReleaseVersion', extension.hasPreReleaseVersion]);
cksOverlay.push(['extensionHasReleaseVersion', extension.hasReleaseVersion]);
cksOverlay.push(['extensionDisallowInstall', extension.isMalicious || extension.deprecationInfo?.disallowInstall]);
cksOverlay.push(['extensionDisallowInstall', !!extension.deprecationInfo?.disallowInstall]);
cksOverlay.push(['isExtensionAllowed', allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName }) === true]);
cksOverlay.push(['isPreReleaseExtensionAllowed', allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName, prerelease: true }) === true]);
cksOverlay.push(['extensionIsUnsigned', extension.gallery && !extension.gallery.isSigned]);
Expand Down
59 changes: 56 additions & 3 deletions src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import './media/extensionsViewlet.css';
import { localize, localize2 } from '../../../../nls.js';
import { Delayer } from '../../../../base/common/async.js';
import { timeout, Delayer, Promises } from '../../../../base/common/async.js';
import { isCancellationError } from '../../../../base/common/errors.js';
import { createErrorWithActions } from '../../../../base/common/errorMessage.js';
import { IWorkbenchContribution } from '../../../common/contributions.js';
Expand All @@ -18,6 +18,7 @@ import { IInstantiationService, ServicesAccessor } from '../../../../platform/in
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, AutoCheckUpdatesConfigurationKey, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, extensionsSearchActionsMenu, AutoRestartConfigurationKey } from '../common/extensions.js';
import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAction } from './extensionsActions.js';
import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js';
import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from '../../../services/extensionManagement/common/extensionManagement.js';
import { ExtensionsInput } from '../common/extensionsInput.js';
import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, UntrustedWorkspaceUnsupportedExtensionsView, UntrustedWorkspacePartiallySupportedExtensionsView, VirtualWorkspaceUnsupportedExtensionsView, VirtualWorkspacePartiallySupportedExtensionsView, DefaultPopularExtensionsView, DeprecatedExtensionsView, SearchMarketplaceExtensionsView, RecentlyUpdatedExtensionsView, OutdatedExtensionsView, StaticQueryExtensionsView, NONE_CATEGORY } from './extensionsViews.js';
Expand All @@ -33,20 +34,22 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from '../../../../platform/contextkey/common/contextkey.js';
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
import { INotificationService, NotificationPriority } from '../../../../platform/notification/common/notification.js';
import { IHostService } from '../../../services/host/browser/host.js';
import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
import { ViewPane } from '../../../browser/parts/views/viewPane.js';
import { Query } from '../common/extensionQuery.js';
import { SuggestEnabledInput } from '../../codeEditor/browser/suggestEnabledInput/suggestEnabledInput.js';
import { alert } from '../../../../base/browser/ui/aria/aria.js';
import { EXTENSION_CATEGORIES } from '../../../../platform/extensions/common/extensions.js';
import { EXTENSION_CATEGORIES, ExtensionType } from '../../../../platform/extensions/common/extensions.js';
import { Registry } from '../../../../platform/registry/common/platform.js';
import { ILabelService } from '../../../../platform/label/common/label.js';
import { MementoObject } from '../../../common/memento.js';
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
import { IPreferencesService } from '../../../services/preferences/common/preferences.js';
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js';
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
import { VirtualWorkspaceContext, WorkbenchStateContext } from '../../../common/contextkeys.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
import { installLocalInRemoteIcon } from './extensionsIcons.js';
Expand All @@ -56,6 +59,7 @@ import { IPaneCompositePartService } from '../../../services/panecomposite/brows
import { coalesce } from '../../../../base/common/arrays.js';
import { extractEditorsAndFilesDropData } from '../../../../platform/dnd/browser/dnd.js';
import { extname } from '../../../../base/common/resources.js';
import { isMalicious } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
import { ILocalizedString } from '../../../../platform/action/common/action.js';
import { registerNavigableContainer } from '../../../browser/actions/widgetNavigationCommands.js';
import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';
Expand Down Expand Up @@ -963,3 +967,52 @@ export class StatusUpdater extends Disposable implements IWorkbenchContribution
}
}
}

export class MaliciousExtensionChecker implements IWorkbenchContribution {

constructor(
@IExtensionManagementService private readonly extensionsManagementService: IExtensionManagementService,
@IHostService private readonly hostService: IHostService,
@ILogService private readonly logService: ILogService,
@INotificationService private readonly notificationService: INotificationService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
if (!this.environmentService.disableExtensions) {
this.loopCheckForMaliciousExtensions();
}
}

private loopCheckForMaliciousExtensions(): void {
this.checkForMaliciousExtensions()
.then(() => timeout(1000 * 60 * 5)) // every five minutes
.then(() => this.loopCheckForMaliciousExtensions());
}

private checkForMaliciousExtensions(): Promise<void> {
return this.extensionsManagementService.getExtensionsControlManifest().then(extensionsControlManifest => {

return this.extensionsManagementService.getInstalled(ExtensionType.User).then(installed => {
const maliciousExtensions = installed.filter(e => isMalicious(e.identifier, extensionsControlManifest));

if (maliciousExtensions.length) {
return Promises.settled(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e).then(() => {
this.notificationService.prompt(
Severity.Warning,
localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", e.identifier.id),
[{
label: localize('reloadNow', "Reload Now"),
run: () => this.hostService.reload()
}],
{
sticky: true,
priority: NotificationPriority.URGENT
}
);
})));
} else {
return Promise.resolve(undefined);
}
}).then(() => undefined);
}, err => this.logService.error(err));
}
}
Loading
Loading