Security: Added user setting for extension policies (#2426)

* added user setting for extension policy

* fix extension action tests
This commit is contained in:
Aditya Bist
2018-09-07 16:58:37 -07:00
committed by Karl Burtram
parent 2b4de52af4
commit 9ea02bf125
7 changed files with 126 additions and 67 deletions

View File

@@ -24,6 +24,9 @@ import { readFile } from 'vs/base/node/pfs';
import { writeFileAndFlushSync } from 'vs/base/node/extfs'; import { writeFileAndFlushSync } from 'vs/base/node/extfs';
import { generateUuid, isUUID } from 'vs/base/common/uuid'; import { generateUuid, isUUID } from 'vs/base/common/uuid';
import { values } from 'vs/base/common/map'; 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 { interface IRawGalleryExtensionFile {
assetType: string; assetType: string;
@@ -367,7 +370,9 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
constructor( constructor(
@IRequestService private requestService: IRequestService, @IRequestService private requestService: IRequestService,
@IEnvironmentService private environmentService: IEnvironmentService, @IEnvironmentService private environmentService: IEnvironmentService,
@ITelemetryService private telemetryService: ITelemetryService @ITelemetryService private telemetryService: ITelemetryService,
// {{SQL CARBON EDIT}}
@IConfigurationService private configurationService: IConfigurationService
) { ) {
const config = product.extensionsGallery; const config = product.extensionsGallery;
this.extensionsGalleryUrl = config && config.serviceUrl; this.extensionsGalleryUrl = config && config.serviceUrl;
@@ -508,6 +513,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
} }
let actualTotal = filteredExtensions.length; let actualTotal = filteredExtensions.length;
// {{SQL CARBON EDIT}}
let extensionPolicy = this.configurationService.getValue<string>(ExtensionsPolicyKey);
if (extensionPolicy === ExtensionsPolicy.allowMicrosoft) {
filteredExtensions = filteredExtensions.filter(ext => ext.publisher && ext.publisher.displayName === 'Microsoft');
}
return { galleryExtensions: filteredExtensions, total: actualTotal }; return { galleryExtensions: filteredExtensions, total: actualTotal };
} }
@@ -551,7 +562,9 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
headers headers
}).then(context => { }).then(context => {
if (context.res.statusCode >= 400 && context.res.statusCode < 500) { // {{SQL CARBON EDIT}}
let extensionPolicy: string = this.configurationService.getValue<string>(ExtensionsPolicyKey);
if (context.res.statusCode >= 400 && context.res.statusCode < 500 || extensionPolicy === ExtensionsPolicy.allowNone) {
return { galleryExtensions: [], total: 0 }; return { galleryExtensions: [], total: 0 };
} }

View File

@@ -101,6 +101,8 @@ export const AutoUpdateConfigurationKey = 'extensions.autoUpdate';
export const AutoCheckUpdatesConfigurationKey = 'extensions.autoCheckUpdates'; export const AutoCheckUpdatesConfigurationKey = 'extensions.autoCheckUpdates';
export const ShowRecommendationsOnlyOnDemandKey = 'extensions.showRecommendationsOnlyOnDemand'; export const ShowRecommendationsOnlyOnDemandKey = 'extensions.showRecommendationsOnlyOnDemand';
export const CloseExtensionDetailsOnViewChangeKey = 'extensions.closeExtensionDetailsOnViewChange'; export const CloseExtensionDetailsOnViewChangeKey = 'extensions.closeExtensionDetailsOnViewChange';
// {{SQL CARBON EDIT}}
export const ExtensionsPolicyKey = 'extensions.extensionsPolicy';
export interface IExtensionsConfiguration { export interface IExtensionsConfiguration {
autoUpdate: boolean; autoUpdate: boolean;
@@ -108,4 +110,13 @@ export interface IExtensionsConfiguration {
ignoreRecommendations: boolean; ignoreRecommendations: boolean;
showRecommendationsOnlyOnDemand: boolean; showRecommendationsOnlyOnDemand: boolean;
closeExtensionDetailsOnViewChange: boolean; closeExtensionDetailsOnViewChange: boolean;
// {{SQL CARBON EDIT}}
extensionsPolicy: string;
} }
// {{SQL CARBON EDIT}}
export enum ExtensionsPolicy {
allowAll = 'allowAll',
allowNone = 'allowNone',
allowMicrosoft = 'allowMicrosoft'
}

View File

@@ -23,7 +23,8 @@ import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsA
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IFileService } from 'vs/platform/files/common/files'; 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 { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as pfs from 'vs/base/node/pfs'; import * as pfs from 'vs/base/node/pfs';
@@ -111,7 +112,9 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
) { ) {
super(); super();
if (!this.isEnabled()) { // {{SQL CARBON EDIT}}
let extensionPolicy: string = this.configurationService.getValue<string>(ExtensionsPolicyKey);
if (!this.isEnabled() || extensionPolicy === ExtensionsPolicy.allowNone) {
return; return;
} }

View File

@@ -17,7 +17,8 @@ import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-bro
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/parts/output/common/output'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/parts/output/common/output';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; 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 { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
import { import {
OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction,
@@ -231,6 +232,13 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
type: 'boolean', type: 'boolean',
description: localize('extensionsCloseExtensionDetailsOnViewChange', "When enabled, editors with extension details will be automatically closed upon navigating away from the Extensions View."), description: localize('extensionsCloseExtensionDetailsOnViewChange', "When enabled, editors with extension details will be automatically closed upon navigating away from the Extensions View."),
default: false 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
} }
} }
}); });

View File

@@ -16,7 +16,8 @@ import * as json from 'vs/base/common/json';
import { ActionItem, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionItem, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; 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 { 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 { 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'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@@ -2660,72 +2661,82 @@ export class InstallVSIXAction extends Action {
@INotificationService private notificationService: INotificationService, @INotificationService private notificationService: INotificationService,
@IWindowService private windowService: IWindowService, @IWindowService private windowService: IWindowService,
// {{SQL CARBON EDIT}} // {{SQL CARBON EDIT}}
@IConfigurationService private configurationService: IConfigurationService,
@IStorageService private storageService: IStorageService @IStorageService private storageService: IStorageService
) { ) {
super(id, label, 'extension-action install-vsix', true); super(id, label, 'extension-action install-vsix', true);
} }
run(): TPromise<any> { run(): TPromise<any> {
return this.windowService.showOpenDialog({ // {{SQL CARBON EDIT}}
title: localize('installFromVSIX', "Install from VSIX"), let extensionPolicy = this.configurationService.getValue<string>(ExtensionsPolicyKey);
filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], if (extensionPolicy === ExtensionsPolicy.allowAll) {
properties: ['openFile'], return this.windowService.showOpenDialog({
buttonLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) title: localize('installFromVSIX', "Install from VSIX"),
}).then(result => { filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }],
if (!result) { properties: ['openFile'],
return TPromise.as(null); buttonLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install"))
} }).then(result => {
return TPromise.join(result.map(vsix => { if (!result) {
// {{SQL CARBON EDIT}} return TPromise.as(null);
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()
}]
);
});
} }
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);
}
}));
});
} }
} }

View File

@@ -26,7 +26,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWindowService } from 'vs/platform/windows/common/windows';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import URI from 'vs/base/common/uri'; 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 { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
@@ -701,6 +702,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
} }
install(extension: string | IExtension): TPromise<void> { install(extension: string | IExtension): TPromise<void> {
// {{SQL CARBON EDIT}}
let extensionPolicy = this.configurationService.getValue<string>(ExtensionsPolicyKey);
if (typeof extension === 'string') { if (typeof extension === 'string') {
return this.progressService.withProgress({ return this.progressService.withProgress({
location: ProgressLocation.Extensions, location: ProgressLocation.Extensions,
@@ -729,7 +732,16 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
title: nls.localize('installingMarketPlaceExtension', 'Installing extension from Marketplace....'), title: nls.localize('installingMarketPlaceExtension', 'Installing extension from Marketplace....'),
source: `${extension.id}` source: `${extension.id}`
// {{SQL CARBON EDIT}} // {{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}} // {{SQL CARBON EDIT}}

View File

@@ -60,7 +60,8 @@ suite('ExtensionsActions Test', () => {
instantiationService.stub(IWindowService, TestWindowService); instantiationService.stub(IWindowService, TestWindowService);
instantiationService.stub(IWorkspaceContextService, new TestContextService()); 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); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);