From 9ea02bf125ae27517afd51af247bf7d69845cacd Mon Sep 17 00:00:00 2001 From: Aditya Bist Date: Fri, 7 Sep 2018 16:58:37 -0700 Subject: [PATCH] Security: Added user setting for extension policies (#2426) * added user setting for extension policy * fix extension action tests --- .../node/extensionGalleryService.ts | 17 ++- .../parts/extensions/common/extensions.ts | 11 ++ .../electron-browser/extensionTipsService.ts | 7 +- .../extensions.contribution.ts | 10 +- .../electron-browser/extensionsActions.ts | 129 ++++++++++-------- .../node/extensionsWorkbenchService.ts | 16 ++- .../extensionsActions.test.ts | 3 +- 7 files changed, 126 insertions(+), 67 deletions(-) diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index 0082188ab9..22e7a9559c 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -24,6 +24,9 @@ import { readFile } from 'vs/base/node/pfs'; import { writeFileAndFlushSync } from 'vs/base/node/extfs'; import { generateUuid, isUUID } from 'vs/base/common/uuid'; import { values } from 'vs/base/common/map'; +// {{SQL CARBON EDIT}} +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/workbench/parts/extensions/common/extensions'; interface IRawGalleryExtensionFile { assetType: string; @@ -367,7 +370,9 @@ export class ExtensionGalleryService implements IExtensionGalleryService { constructor( @IRequestService private requestService: IRequestService, @IEnvironmentService private environmentService: IEnvironmentService, - @ITelemetryService private telemetryService: ITelemetryService + @ITelemetryService private telemetryService: ITelemetryService, + // {{SQL CARBON EDIT}} + @IConfigurationService private configurationService: IConfigurationService ) { const config = product.extensionsGallery; this.extensionsGalleryUrl = config && config.serviceUrl; @@ -508,6 +513,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } let actualTotal = filteredExtensions.length; + + // {{SQL CARBON EDIT}} + let extensionPolicy = this.configurationService.getValue(ExtensionsPolicyKey); + if (extensionPolicy === ExtensionsPolicy.allowMicrosoft) { + filteredExtensions = filteredExtensions.filter(ext => ext.publisher && ext.publisher.displayName === 'Microsoft'); + } return { galleryExtensions: filteredExtensions, total: actualTotal }; } @@ -551,7 +562,9 @@ export class ExtensionGalleryService implements IExtensionGalleryService { headers }).then(context => { - if (context.res.statusCode >= 400 && context.res.statusCode < 500) { + // {{SQL CARBON EDIT}} + let extensionPolicy: string = this.configurationService.getValue(ExtensionsPolicyKey); + if (context.res.statusCode >= 400 && context.res.statusCode < 500 || extensionPolicy === ExtensionsPolicy.allowNone) { return { galleryExtensions: [], total: 0 }; } diff --git a/src/vs/workbench/parts/extensions/common/extensions.ts b/src/vs/workbench/parts/extensions/common/extensions.ts index 7153350cc7..cb14b07a66 100644 --- a/src/vs/workbench/parts/extensions/common/extensions.ts +++ b/src/vs/workbench/parts/extensions/common/extensions.ts @@ -101,6 +101,8 @@ export const AutoUpdateConfigurationKey = 'extensions.autoUpdate'; export const AutoCheckUpdatesConfigurationKey = 'extensions.autoCheckUpdates'; export const ShowRecommendationsOnlyOnDemandKey = 'extensions.showRecommendationsOnlyOnDemand'; export const CloseExtensionDetailsOnViewChangeKey = 'extensions.closeExtensionDetailsOnViewChange'; +// {{SQL CARBON EDIT}} +export const ExtensionsPolicyKey = 'extensions.extensionsPolicy'; export interface IExtensionsConfiguration { autoUpdate: boolean; @@ -108,4 +110,13 @@ export interface IExtensionsConfiguration { ignoreRecommendations: boolean; showRecommendationsOnlyOnDemand: boolean; closeExtensionDetailsOnViewChange: boolean; + // {{SQL CARBON EDIT}} + extensionsPolicy: string; } + +// {{SQL CARBON EDIT}} +export enum ExtensionsPolicy { + allowAll = 'allowAll', + allowNone = 'allowNone', + allowMicrosoft = 'allowMicrosoft' +} \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 79b75b8ff9..67c1d9f544 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -23,7 +23,8 @@ import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsA import Severity from 'vs/base/common/severity'; import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IFileService } from 'vs/platform/files/common/files'; -import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions'; +// {{SQL CARBON EDIT}} +import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/parts/extensions/common/extensions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as pfs from 'vs/base/node/pfs'; @@ -111,7 +112,9 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ) { super(); - if (!this.isEnabled()) { + // {{SQL CARBON EDIT}} + let extensionPolicy: string = this.configurationService.getValue(ExtensionsPolicyKey); + if (!this.isEnabled() || extensionPolicy === ExtensionsPolicy.allowNone) { return; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts index 44c8971fc0..add818bf1d 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts @@ -17,7 +17,8 @@ import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-bro import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/parts/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService } from '../common/extensions'; +// {{SQL CARBON EDIT}} +import { VIEWLET_ID, IExtensionsWorkbenchService, ExtensionsPolicy } from '../common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, @@ -231,6 +232,13 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'boolean', description: localize('extensionsCloseExtensionDetailsOnViewChange', "When enabled, editors with extension details will be automatically closed upon navigating away from the Extensions View."), default: false + }, + // {{SQL CARBON EDIT}} + 'extensions.extensionsPolicy': { + type: 'string', + description: localize('extensionsPolicy', "Sets the security policy for downloading extensions."), + scope: ConfigurationScope.APPLICATION, + default: ExtensionsPolicy.allowAll } } }); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index fd32a1c89e..6274129206 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -16,7 +16,8 @@ import * as json from 'vs/base/common/json'; import { ActionItem, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; +// {{SQL CARBON EDIT}} +import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/parts/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate'; import { LocalExtensionType, IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionManagementServer, IExtensionManagementServerService, IExtensionRecommendation, ExtensionRecommendationSource, IExtensionGalleryService, IGalleryExtension, ILocalExtension, IExtensionsConfigContent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -2660,72 +2661,82 @@ export class InstallVSIXAction extends Action { @INotificationService private notificationService: INotificationService, @IWindowService private windowService: IWindowService, // {{SQL CARBON EDIT}} + @IConfigurationService private configurationService: IConfigurationService, @IStorageService private storageService: IStorageService ) { super(id, label, 'extension-action install-vsix', true); } run(): TPromise { - return this.windowService.showOpenDialog({ - title: localize('installFromVSIX', "Install from VSIX"), - filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], - properties: ['openFile'], - buttonLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) - }).then(result => { - if (!result) { - return TPromise.as(null); - } - return TPromise.join(result.map(vsix => { - // {{SQL CARBON EDIT}} - if (!this.storageService.getBoolean(vsix)) { - this.notificationService.prompt( - Severity.Warning, - localize('thirdPartyExtension.vsix', 'This is a third party extension and might involve security risks. Are you sure you want to install this extension?'), - [ - { - label: localize('thirdPartExt.yes', 'Yes'), - run: () => { - this.extensionsWorkbenchService.install(vsix).then(() => { - this.notificationService.prompt( - Severity.Info, - localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), - [{ - label: localize('InstallVSIXAction.reloadNow', "Reload Now"), - run: () => this.windowService.reloadWindow() - }] - ); - }); - } - }, - { - label: localize('thirdPartyExt.no', 'No'), - run: () => { return TPromise.as(null); } - }, - { - label: localize('thirdPartyExt.dontShowAgain', 'Don\'t Show Again'), - isSecondary: true, - run: () => { - this.storageService.store(vsix, true); - return TPromise.as(null); - } - } - ] - ); - } else { - this.extensionsWorkbenchService.install(vsix).then(() => { - this.notificationService.prompt( - Severity.Info, - localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), - [{ - label: localize('InstallVSIXAction.reloadNow', "Reload Now"), - run: () => this.windowService.reloadWindow() - }] - ); - }); + // {{SQL CARBON EDIT}} + let extensionPolicy = this.configurationService.getValue(ExtensionsPolicyKey); + if (extensionPolicy === ExtensionsPolicy.allowAll) { + return this.windowService.showOpenDialog({ + title: localize('installFromVSIX', "Install from VSIX"), + filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], + properties: ['openFile'], + buttonLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) + }).then(result => { + if (!result) { + return TPromise.as(null); } + return TPromise.join(result.map(vsix => { + // {{SQL CARBON EDIT}} + if (!this.storageService.getBoolean(vsix)) { + this.notificationService.prompt( + Severity.Warning, + localize('thirdPartyExtension.vsix', 'This is a third party extension and might involve security risks. Are you sure you want to install this extension?'), + [ + { + label: localize('thirdPartExt.yes', 'Yes'), + run: () => { + this.extensionsWorkbenchService.install(vsix).then(() => { + this.notificationService.prompt( + Severity.Info, + localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), + [{ + label: localize('InstallVSIXAction.reloadNow', "Reload Now"), + run: () => this.windowService.reloadWindow() + }] + ); + }); + } + }, + { + label: localize('thirdPartyExt.no', 'No'), + run: () => { return TPromise.as(null); } + }, + { + label: localize('thirdPartyExt.dontShowAgain', 'Don\'t Show Again'), + isSecondary: true, + run: () => { + this.storageService.store(vsix, true); + return TPromise.as(null); + } + } + ] + ); + } else { + this.extensionsWorkbenchService.install(vsix).then(() => { + this.notificationService.prompt( + Severity.Info, + localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), + [{ + label: localize('InstallVSIXAction.reloadNow', "Reload Now"), + run: () => this.windowService.reloadWindow() + }] + ); + }); + } + + })); + }); + // {{SQL CARBON EDIT}} + } else { + this.notificationService.error(localize('InstallVSIXAction.allowNone', 'Your extension policy does not allow downloading extensions. Please change your extension policy and try again.')); + return TPromise.as(null); + } - })); - }); } } diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 69b86acdba..88178912b6 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -26,7 +26,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IWindowService } from 'vs/platform/windows/common/windows'; import Severity from 'vs/base/common/severity'; import URI from 'vs/base/common/uri'; -import { IExtension, IExtensionDependencies, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; +// {{SQL CARBON EDIT}} +import { IExtension, IExtensionDependencies, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/parts/extensions/common/extensions'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; @@ -701,6 +702,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, } install(extension: string | IExtension): TPromise { + // {{SQL CARBON EDIT}} + let extensionPolicy = this.configurationService.getValue(ExtensionsPolicyKey); if (typeof extension === 'string') { return this.progressService.withProgress({ location: ProgressLocation.Extensions, @@ -729,7 +732,16 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, title: nls.localize('installingMarketPlaceExtension', 'Installing extension from Marketplace....'), source: `${extension.id}` // {{SQL CARBON EDIT}} - }, () => this.downloadOrBrowse(ext)); + }, () => { + if (extensionPolicy === ExtensionsPolicy.allowMicrosoft) { + if (ext.publisherDisplayName === 'Microsoft') { + return this.downloadOrBrowse(ext); + } else { + return TPromise.as(null); + } + } + return this.downloadOrBrowse(ext); + }); } // {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts index 24ee411369..c3781f5186 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts @@ -60,7 +60,8 @@ suite('ExtensionsActions Test', () => { instantiationService.stub(IWindowService, TestWindowService); instantiationService.stub(IWorkspaceContextService, new TestContextService()); - instantiationService.stub(IConfigurationService, { onDidUpdateConfiguration: () => { }, onDidChangeConfiguration: () => { }, getConfiguration: () => ({}) }); + // {{SQL CARBON EDIT}} + instantiationService.stub(IConfigurationService, { onDidUpdateConfiguration: () => { }, onDidChangeConfiguration: () => { }, getConfiguration: () => ({}), getValue: () => { } }); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);