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 { 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<string>(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<string>(ExtensionsPolicyKey);
if (context.res.statusCode >= 400 && context.res.statusCode < 500 || extensionPolicy === ExtensionsPolicy.allowNone) {
return { galleryExtensions: [], total: 0 };
}

View File

@@ -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'
}

View File

@@ -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<string>(ExtensionsPolicyKey);
if (!this.isEnabled() || extensionPolicy === ExtensionsPolicy.allowNone) {
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 { 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<IConfigurationRegistry>(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
}
}
});

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 { 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<any> {
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<string>(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);
}
}));
});
}
}

View File

@@ -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<void> {
// {{SQL CARBON EDIT}}
let extensionPolicy = this.configurationService.getValue<string>(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}}

View File

@@ -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);