未验证 提交 b173ec7e 编写于 作者: J João Moreno 提交者: GitHub

Merge pull request #42087 from Microsoft/joao/extension-report

Handle reported extensions
......@@ -232,7 +232,6 @@ export enum StatisticType {
export interface IReportedExtension {
id: IExtensionIdentifier;
malicious: boolean;
slow: boolean;
}
export interface IExtensionGalleryService {
......@@ -280,6 +279,7 @@ export interface IExtensionManagementService {
installFromGallery(extension: IGalleryExtension): TPromise<void>;
uninstall(extension: ILocalExtension, force?: boolean): TPromise<void>;
getInstalled(type?: LocalExtensionType): TPromise<ILocalExtension[]>;
getExtensionsReport(): TPromise<IReportedExtension[]>;
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): TPromise<ILocalExtension>;
}
......
......@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata } from './extensionManagement';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from './extensionManagement';
import Event, { buffer } from 'vs/base/common/event';
export interface IExtensionManagementChannel extends IChannel {
......@@ -19,6 +19,7 @@ export interface IExtensionManagementChannel extends IChannel {
call(command: 'installFromGallery', extension: IGalleryExtension): TPromise<void>;
call(command: 'uninstall', args: [ILocalExtension, boolean]): TPromise<void>;
call(command: 'getInstalled'): TPromise<ILocalExtension[]>;
call(command: 'getExtensionsReport'): TPromise<IReportedExtension[]>;
call(command: string, arg?: any): TPromise<any>;
}
......@@ -47,6 +48,7 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel {
case 'uninstall': return this.service.uninstall(arg[0], arg[1]);
case 'getInstalled': return this.service.getInstalled(arg);
case 'updateMetadata': return this.service.updateMetadata(arg[0], arg[1]);
case 'getExtensionsReport': return this.service.getExtensionsReport();
}
return undefined;
}
......@@ -89,4 +91,8 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): TPromise<ILocalExtension> {
return this.channel.call('updateMetadata', [local, metadata]);
}
getExtensionsReport(): TPromise<IReportedExtension[]> {
return this.channel.call('getExtensionsReport');
}
}
\ No newline at end of file
......@@ -5,7 +5,7 @@
'use strict';
import { ILocalExtension, IGalleryExtension, EXTENSION_IDENTIFIER_REGEX, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILocalExtension, IGalleryExtension, EXTENSION_IDENTIFIER_REGEX, IExtensionIdentifier, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean {
if (a.uuid && b.uuid) {
......@@ -101,4 +101,16 @@ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension):
}
export const BetterMergeDisabledNowKey = 'extensions/bettermergedisablednow';
export const BetterMergeId = 'pprice.better-merge';
\ No newline at end of file
export const BetterMergeId = 'pprice.better-merge';
export function getMaliciousExtensionsSet(report: IReportedExtension[]): Set<string> {
const result = new Set<string>();
for (const extension of report) {
if (extension.malicious) {
result.add(extension.id.id);
}
}
return result;
}
\ No newline at end of file
......@@ -743,12 +743,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
map.set(id, ext);
}
for (const id of result.slow) {
const ext = map.get(id) || { id: { id }, malicious: false, slow: true };
ext.slow = true;
map.set(id, ext);
}
return TPromise.as(values(map));
});
});
......
......@@ -19,9 +19,10 @@ import {
IGalleryExtension, IExtensionManifest, IGalleryMetadata,
InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, LocalExtensionType,
StatisticType,
IExtensionIdentifier
IExtensionIdentifier,
IReportedExtension
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { localizeManifest } from '../common/extensionNls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Limiter } from 'vs/base/common/async';
......@@ -103,8 +104,8 @@ export class ExtensionManagementService implements IExtensionManagementService {
private extensionsPath: string;
private uninstalledPath: string;
private userDataPath: string;
private uninstalledFileLimiter: Limiter<void>;
private reportedExtensions: TPromise<IReportedExtension[]> | undefined;
private disposables: IDisposable[] = [];
private readonly _onInstallExtension = new Emitter<InstallExtensionEvent>();
......@@ -122,20 +123,19 @@ export class ExtensionManagementService implements IExtensionManagementService {
onDidUninstallExtension: Event<DidUninstallExtensionEvent> = this._onDidUninstallExtension.event;
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IChoiceService private choiceService: IChoiceService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@ILogService private logService: ILogService,
@ILogService private logService: ILogService
) {
this.extensionsPath = environmentService.extensionsPath;
this.uninstalledPath = path.join(this.extensionsPath, '.obsolete');
this.userDataPath = environmentService.userDataPath;
this.uninstalledFileLimiter = new Limiter(1);
this.disposables.push(toDisposable(() => this.installingExtensions.clear()));
}
private deleteExtensionsManifestCache(): void {
const cacheFolder = path.join(this.userDataPath, MANIFEST_CACHE_FOLDER);
const cacheFolder = path.join(this.environmentService.userDataPath, MANIFEST_CACHE_FOLDER);
const cacheFile = path.join(cacheFolder, USER_MANIFEST_CACHE_FILE);
pfs.del(cacheFile).done(() => { }, () => { });
......@@ -269,9 +269,22 @@ export class ExtensionManagementService implements IExtensionManagementService {
private downloadAndInstallExtension(extension: IGalleryExtension): TPromise<ILocalExtension> {
let installingExtension = this.installingExtensions.get(extension.identifier.id);
if (!installingExtension) {
installingExtension = this.downloadInstallableExtension(extension).then(installableExtension => this.installExtension(installableExtension));
installingExtension = this.reportedExtensions
.then(report => {
if (getMaliciousExtensionsSet(report).has(extension.identifier.id)) {
throw new Error(nls.localize('malicious extension', "Can't install extension since it was reported to be malicious."));
} else {
return extension;
}
})
.then(extension => this.downloadInstallableExtension(extension))
.then(installableExtension => this.installExtension(installableExtension));
this.installingExtensions.set(extension.identifier.id, installingExtension);
installingExtension.then(local => { this.installingExtensions.delete(extension.identifier.id); return local; }, e => { this.installingExtensions.delete(extension.identifier.id); return TPromise.wrapError(e); });
installingExtension.then(
local => { this.installingExtensions.delete(extension.identifier.id); return local; },
e => { this.installingExtensions.delete(extension.identifier.id); return TPromise.wrapError(e); }
);
}
return installingExtension;
}
......@@ -367,6 +380,8 @@ export class ExtensionManagementService implements IExtensionManagementService {
}
private installExtension(installableExtension: InstallableExtension): TPromise<ILocalExtension> {
// BLOCK REPORTED EXTENSIONS HERE?
return this.unsetUninstalledAndGetLocal(installableExtension.id)
.then(
local => {
......@@ -776,6 +791,32 @@ export class ExtensionManagementService implements IExtensionManagementService {
});
}
private lastReportTimestamp = 0;
getExtensionsReport(): TPromise<IReportedExtension[]> {
const now = new Date().getTime();
if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
this.reportedExtensions = this.updateReportCache();
this.lastReportTimestamp = now;
}
return this.reportedExtensions;
}
private updateReportCache(): TPromise<IReportedExtension[]> {
this.logService.trace('ExtensionManagementService.refreshReportedCache');
return this.galleryService.getExtensionsReport()
.then(result => {
this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
return result;
}, err => {
this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
return [];
});
}
dispose() {
this.disposables = dispose(this.disposables);
}
......
......@@ -33,7 +33,7 @@ import { Renderer, DataSource, Controller } from 'vs/workbench/parts/extensions/
import { RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { EditorOptions } from 'vs/workbench/common/editor';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, BuiltinStatusLabelAction, ReloadAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, BuiltinStatusLabelAction, ReloadAction, MaliciousStatusLabelAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import WebView from 'vs/workbench/parts/html/browser/webview';
import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
......@@ -344,6 +344,7 @@ export class ExtensionEditor extends BaseEditor {
this.transientDisposables.push(ratings);
const builtinStatusAction = this.instantiationService.createInstance(BuiltinStatusLabelAction);
const maliciousStatusAction = this.instantiationService.createInstance(MaliciousStatusLabelAction, true);
const installAction = this.instantiationService.createInstance(CombinedInstallAction);
const updateAction = this.instantiationService.createInstance(UpdateAction);
const enableAction = this.instantiationService.createInstance(EnableAction);
......@@ -352,14 +353,15 @@ export class ExtensionEditor extends BaseEditor {
installAction.extension = extension;
builtinStatusAction.extension = extension;
maliciousStatusAction.extension = extension;
updateAction.extension = extension;
enableAction.extension = extension;
disableAction.extension = extension;
reloadAction.extension = extension;
this.extensionActionBar.clear();
this.extensionActionBar.push([reloadAction, updateAction, enableAction, disableAction, installAction, builtinStatusAction], { icon: true, label: true });
this.transientDisposables.push(enableAction, updateAction, reloadAction, disableAction, installAction, builtinStatusAction);
this.extensionActionBar.push([reloadAction, updateAction, enableAction, disableAction, installAction, builtinStatusAction, maliciousStatusAction], { icon: true, label: true });
this.transientDisposables.push(enableAction, updateAction, reloadAction, disableAction, installAction, builtinStatusAction, maliciousStatusAction);
this.navbar.clear();
this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);
......
......@@ -1555,6 +1555,34 @@ export class BuiltinStatusLabelAction extends Action {
}
}
export class MaliciousStatusLabelAction extends Action {
private static readonly Class = 'malicious-status';
private _extension: IExtension;
get extension(): IExtension { return this._extension; }
set extension(extension: IExtension) { this._extension = extension; this.update(); }
constructor(long: boolean) {
const tooltip = localize('malicious tooltip', "This extension was reported to be malicious.");
const label = long ? tooltip : localize('malicious', "Malicious");
super('extensions.install', label, '', false);
this.tooltip = localize('malicious tooltip', "This extension was reported to be malicious.");
}
private update(): void {
if (this.extension && this.extension.isMalicious) {
this.class = `${MaliciousStatusLabelAction.Class} malicious`;
} else {
this.class = `${MaliciousStatusLabelAction.Class} not-malicious`;
}
}
run(): TPromise<any> {
return TPromise.as(null);
}
}
export class DisableAllAction extends Action {
static readonly ID = 'workbench.extensions.action.disableAll';
......
......@@ -16,7 +16,7 @@ import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
import { once } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions';
import { InstallAction, UpdateAction, BuiltinStatusLabelAction, ManageExtensionAction, ReloadAction, extensionButtonProminentBackground, extensionButtonProminentForeground } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { InstallAction, UpdateAction, BuiltinStatusLabelAction, ManageExtensionAction, ReloadAction, extensionButtonProminentBackground, extensionButtonProminentForeground, MaliciousStatusLabelAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { Label, RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
......@@ -97,13 +97,14 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
const ratingsWidget = this.instantiationService.createInstance(RatingsWidget, ratings, { small: true });
const builtinStatusAction = this.instantiationService.createInstance(BuiltinStatusLabelAction);
const maliciousStatusAction = this.instantiationService.createInstance(MaliciousStatusLabelAction, false);
const installAction = this.instantiationService.createInstance(InstallAction);
const updateAction = this.instantiationService.createInstance(UpdateAction);
const reloadAction = this.instantiationService.createInstance(ReloadAction);
const manageAction = this.instantiationService.createInstance(ManageExtensionAction);
actionbar.push([reloadAction, updateAction, installAction, builtinStatusAction, manageAction], actionOptions);
const disposables = [versionWidget, installCountWidget, ratingsWidget, builtinStatusAction, updateAction, reloadAction, manageAction, actionbar, bookmarkStyler];
actionbar.push([reloadAction, updateAction, installAction, builtinStatusAction, maliciousStatusAction, manageAction], actionOptions);
const disposables = [versionWidget, installCountWidget, ratingsWidget, builtinStatusAction, maliciousStatusAction, updateAction, reloadAction, manageAction, actionbar, bookmarkStyler];
return {
root, element, icon, name, installCount, ratings, author, description, disposables,
......@@ -113,6 +114,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
installCountWidget.extension = extension;
ratingsWidget.extension = extension;
builtinStatusAction.extension = extension;
maliciousStatusAction.extension = extension;
installAction.extension = extension;
updateAction.extension = extension;
reloadAction.extension = extension;
......
......@@ -11,6 +11,7 @@ import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
import { IExtensionsViewlet, VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
class SimpleEntry extends QuickOpenEntry {
......@@ -75,7 +76,8 @@ export class GalleryExtensionsHandler extends QuickOpenHandler {
constructor(
@IViewletService private viewletService: IViewletService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IExtensionManagementService private extensionsService: IExtensionManagementService
@IExtensionManagementService private extensionsService: IExtensionManagementService,
@IMessageService private messageService: IMessageService
) {
super();
}
......@@ -97,7 +99,8 @@ export class GalleryExtensionsHandler extends QuickOpenHandler {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => viewlet.search(`@id:${text}`))
.done(() => this.extensionsService.installFromGallery(galleryExtension));
.then(() => this.extensionsService.installFromGallery(galleryExtension))
.done(null, err => this.messageService.show(Severity.Error, err));
};
entries.push(new SimpleEntry(label, action));
......
......@@ -30,11 +30,13 @@
.monaco-action-bar .action-item.disabled .action-label.extension-action.enable,
.monaco-action-bar .action-item.disabled .action-label.extension-action.disable,
.monaco-action-bar .action-item.disabled .action-label.extension-action.reload,
.monaco-action-bar .action-item.disabled .action-label.built-in-status.user {
.monaco-action-bar .action-item.disabled .action-label.built-in-status.user,
.monaco-action-bar .action-item.disabled .action-label.malicious-status.not-malicious {
display: none;
}
.monaco-action-bar .action-item .action-label.built-in-status {
.monaco-action-bar .action-item .action-label.built-in-status
.monaco-action-bar .action-item .action-label.malicious-status {
border-radius: 4px;
color: inherit;
background-color: transparent;
......@@ -44,7 +46,8 @@
line-height: initial;
}
.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.built-in-status {
.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.built-in-status,
.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.malicious-status {
font-weight: normal;
}
......
......@@ -52,6 +52,7 @@ export interface IExtension {
getReadme(): TPromise<string>;
getChangelog(): TPromise<string>;
local?: ILocalExtension;
isMalicious: boolean;
}
export interface IExtensionDependencies {
......
......@@ -28,7 +28,7 @@ import { OpenExtensionsFolderAction, InstallVSIXAction } from 'vs/workbench/part
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { ExtensionEditor } from 'vs/workbench/parts/extensions/browser/extensionEditor';
import { StatusUpdater, ExtensionsViewlet } from 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet';
import { StatusUpdater, ExtensionsViewlet, MaliciousExtensionChecker } from 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet';
import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import jsonContributionRegistry = require('vs/platform/jsonschemas/common/jsonContributionRegistry');
......@@ -52,6 +52,7 @@ registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService);
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Running);
workbenchRegistry.registerWorkbenchContribution(MaliciousExtensionChecker, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(ConfigureRecommendedExtensionsCommandsContributor, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase.Running);
workbenchRegistry.registerWorkbenchContribution(BetterMergeDisabled, LifecyclePhase.Running);
......
......@@ -51,6 +51,9 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { getGalleryExtensionIdFromLocal, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ILogService } from 'vs/platform/log/common/log';
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions';
interface SearchInputEvent extends Event {
target: HTMLInputElement;
......@@ -452,3 +455,49 @@ export class StatusUpdater implements IWorkbenchContribution {
dispose(this.badgeHandle);
}
}
export class MaliciousExtensionChecker implements IWorkbenchContribution {
private disposables: IDisposable[];
constructor(
@IExtensionManagementService private extensionsManagementService: IExtensionManagementService,
@IInstantiationService private instantiationService: IInstantiationService,
@ILogService private logService: ILogService,
@IMessageService private messageService: IMessageService
) {
this.loopCheckForMaliciousExtensions();
}
private loopCheckForMaliciousExtensions(): void {
this.checkForMaliciousExtensions()
.then(() => TPromise.timeout(1000 * 60 * 5)) // every five minutes
.then(() => this.loopCheckForMaliciousExtensions());
}
private checkForMaliciousExtensions(): TPromise<any> {
return this.extensionsManagementService.getExtensionsReport().then(report => {
const maliciousSet = getMaliciousExtensionsSet(report);
return this.extensionsManagementService.getInstalled(LocalExtensionType.User).then(installed => {
const maliciousExtensions = installed
.filter(e => maliciousSet.has(getGalleryExtensionIdFromLocal(e)));
if (maliciousExtensions.length) {
return TPromise.join(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => {
this.messageService.show(Severity.Warning, {
message: localize('malicious warning', "We have uninstalled '{0}' which was reported to be malicious.", getGalleryExtensionIdFromLocal(e)),
actions: [this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('reloadNow', "Reload Now"))]
});
})));
} else {
return TPromise.as(null);
}
});
}, err => this.logService.error(err));
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
\ No newline at end of file
......@@ -22,7 +22,7 @@ import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest,
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionIdFromLocal, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getGalleryExtensionIdFromLocal, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWindowService } from 'vs/platform/windows/common/windows';
......@@ -37,8 +37,8 @@ import product from 'vs/platform/node/product';
import { ILogService } from 'vs/platform/log/common/log';
import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress';
interface IExtensionStateProvider {
(extension: Extension): ExtensionState;
interface IExtensionStateProvider<T> {
(extension: Extension): T;
}
class Extension implements IExtension {
......@@ -47,7 +47,7 @@ class Extension implements IExtension {
constructor(
private galleryService: IExtensionGalleryService,
private stateProvider: IExtensionStateProvider,
private stateProvider: IExtensionStateProvider<ExtensionState>,
public local: ILocalExtension,
public gallery: IGalleryExtension,
private telemetryService: ITelemetryService
......@@ -153,6 +153,8 @@ class Extension implements IExtension {
return this.stateProvider(this);
}
public isMalicious: boolean = false;
get installCount(): number {
return this.gallery ? this.gallery.installCount : null;
}
......@@ -321,7 +323,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
private static readonly SyncPeriod = 1000 * 60 * 60 * 12; // 12 hours
_serviceBrand: any;
private stateProvider: IExtensionStateProvider;
private stateProvider: IExtensionStateProvider<ExtensionState>;
private installing: IActiveExtension[] = [];
private uninstalling: IActiveExtension[] = [];
private installed: Extension[] = [];
......@@ -399,15 +401,19 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
}
queryGallery(options: IQueryOptions = {}): TPromise<IPager<IExtension>> {
return this.galleryService.query(options)
.then(result => mapPager(result, gallery => this.fromGallery(gallery)))
.then(null, err => {
if (/No extension gallery service configured/.test(err.message)) {
return TPromise.as(singlePagePager([]));
}
return this.extensionService.getExtensionsReport().then(report => {
const maliciousSet = getMaliciousExtensionsSet(report);
return this.galleryService.query(options)
.then(result => mapPager(result, gallery => this.fromGallery(gallery, maliciousSet)))
.then(null, err => {
if (/No extension gallery service configured/.test(err.message)) {
return TPromise.as(singlePagePager([]));
}
return TPromise.wrapError<IPager<IExtension>>(err);
});
return TPromise.wrapError<IPager<IExtension>>(err);
});
});
}
loadDependencies(extension: IExtension): TPromise<IExtensionDependencies> {
......@@ -415,38 +421,47 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
return TPromise.wrap<IExtensionDependencies>(null);
}
return this.galleryService.loadAllDependencies((<Extension>extension).dependencies.map(id => <IExtensionIdentifier>{ id }))
.then(galleryExtensions => galleryExtensions.map(galleryExtension => this.fromGallery(galleryExtension)))
.then(extensions => [...this.local, ...extensions])
.then(extensions => {
const map = new Map<string, IExtension>();
for (const extension of extensions) {
map.set(extension.id, extension);
}
return new ExtensionDependencies(extension, extension.id, map);
});
return this.extensionService.getExtensionsReport().then(report => {
const maliciousSet = getMaliciousExtensionsSet(report);
return this.galleryService.loadAllDependencies((<Extension>extension).dependencies.map(id => <IExtensionIdentifier>{ id }))
.then(galleryExtensions => galleryExtensions.map(galleryExtension => this.fromGallery(galleryExtension, maliciousSet)))
.then(extensions => [...this.local, ...extensions])
.then(extensions => {
const map = new Map<string, IExtension>();
for (const extension of extensions) {
map.set(extension.id, extension);
}
return new ExtensionDependencies(extension, extension.id, map);
});
});
}
open(extension: IExtension, sideByside: boolean = false): TPromise<any> {
return this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), null, sideByside);
}
private fromGallery(gallery: IGalleryExtension): Extension {
const installed = this.getInstalledExtensionMatchingGallery(gallery);
private fromGallery(gallery: IGalleryExtension, maliciousExtensionSet: Set<string>): Extension {
let result = this.getInstalledExtensionMatchingGallery(gallery);
if (installed) {
if (result) {
// Loading the compatible version only there is an engine property
// Otherwise falling back to old way so that we will not make many roundtrips
if (gallery.properties.engine) {
this.galleryService.loadCompatibleVersion(gallery)
.then(compatible => compatible ? this.syncLocalWithGalleryExtension(installed, compatible) : null);
.then(compatible => compatible ? this.syncLocalWithGalleryExtension(result, compatible) : null);
} else {
this.syncLocalWithGalleryExtension(installed, gallery);
this.syncLocalWithGalleryExtension(result, gallery);
}
return installed;
} else {
result = new Extension(this.galleryService, this.stateProvider, null, gallery, this.telemetryService);
}
if (maliciousExtensionSet.has(result.id)) {
result.isMalicious = true;
}
return new Extension(this.galleryService, this.stateProvider, null, gallery, this.telemetryService);
return result;
}
private getInstalledExtensionMatchingGallery(gallery: IGalleryExtension): Extension {
......@@ -533,6 +548,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
return false;
}
if (extension.isMalicious) {
return false;
}
return !!(extension as Extension).gallery;
}
......@@ -549,6 +568,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
return undefined;
}
if (extension.isMalicious) {
return TPromise.wrapError<void>(new Error(nls.localize('malicious', "This extension is reported to be malicious.")));
}
const ext = extension as Extension;
const gallery = ext.gallery;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册