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