diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 6442d5433d..67eb200849 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -61,7 +61,7 @@ import { IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensi import { Schemas } from 'vs/base/common/network'; import { ShowRuntimeExtensionsAction } from 'vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor'; import { clearSearchResultsIcon, configureRecommendedIcon, extensionsViewIcon, filterIcon, installWorkspaceRecommendedIcon, refreshIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; -import { ExtensionsPolicy, EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; +import { ExtensionsPolicy, ExtensionsPolicyKey, EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { isArray } from 'vs/base/common/types'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -69,6 +69,7 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { Promises } from 'vs/base/common/async'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -626,6 +627,15 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi } }); + // {{SQL CARBON EDIT}} - extension policy check function + const isExtensionInstallationAllowed = (configurationService: IConfigurationService, notificationService: INotificationService): boolean => { + const allowAll = configurationService.getValue(ExtensionsPolicyKey) === ExtensionsPolicy.allowAll; + if (!allowAll) { + this.notificationService.error(localize('InstallVSIXAction.allowNone', 'Your extension policy does not allow installing extensions. Please change your extension policy and try again.')); + } + return allowAll; + }; + this.registerExtensionAction({ id: SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, title: { value: localize('InstallFromVSIX', "Install from VSIX..."), original: 'Install from VSIX...' }, @@ -642,6 +652,14 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi run: async (accessor: ServicesAccessor) => { const fileDialogService = accessor.get(IFileDialogService); const commandService = accessor.get(ICommandService); + + // {{SQL CARBON EDIT}} - add policy check + const configurationService = accessor.get(IConfigurationService); + const notificationService = accessor.get(INotificationService); + if (!isExtensionInstallationAllowed(configurationService, notificationService)) { + return; + } + const vsixPaths = await fileDialogService.showOpenDialog({ title: localize('installFromVSIX', "Install from VSIX"), filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], @@ -669,12 +687,56 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi const hostService = accessor.get(IHostService); const notificationService = accessor.get(INotificationService); + + // {{SQL CARBON EDIT}} - added policy check and third party extension confirmation. + const storageService = accessor.get(IStorageService); + const configurationService = accessor.get(IConfigurationService); + if (!isExtensionInstallationAllowed(configurationService, notificationService)) { + return; + } const extensions = Array.isArray(resources) ? resources : [resources]; - await Promises.settled(extensions.map(async (vsix) => await extensionsWorkbenchService.install(vsix))) + await Promises.settled(extensions.map(async (vsix) => { + if (!storageService.getBoolean(vsix.fsPath, StorageScope.GLOBAL)) { + const accept = await new Promise(resolve => { + 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: () => resolve(true) + }, + { + label: localize('thirdPartyExt.no', 'No'), + run: () => resolve(false) + }, + { + label: localize('thirdPartyExt.dontShowAgain', 'Don\'t Show Again'), + isSecondary: true, + run: () => { + storageService.store(vsix.fsPath, true, StorageScope.GLOBAL, StorageTarget.MACHINE); + resolve(true); + } + } + ], + { sticky: true } + ); + }); + + if (!accept) { + return undefined; + } + } + return await extensionsWorkbenchService.install(vsix); + })) .then(async (extensions) => { for (const extension of extensions) { + // {{SQL CARBON EDIT}} - Add null check + if (extension === undefined) { + continue; + } const requireReload = !(extension.local && extensionService.canAddExtension(toExtensionDescription(extension.local))); - const message = requireReload ? localize('InstallVSIXAction.successReload', "Completed installing {0} extension from VSIX. Please reload Visual Studio Code to enable it.", extension.displayName || extension.name) + const message = requireReload ? localize('InstallVSIXAction.successReload', "Completed installing {0} extension from VSIX. Please reload Azure Data Studio to enable it.", extension.displayName || extension.name) // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio : localize('InstallVSIXAction.success', "Completed installing {0} extension from VSIX.", extension.displayName || extension.name); const actions = requireReload ? [{ label: localize('InstallVSIXAction.reloadNow', "Reload Now"), diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index cb0216d2bb..a72c66bf02 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -18,7 +18,7 @@ import { IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, I import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensioManagementService, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}} +import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}} import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IFileService, IFileContent } from 'vs/platform/files/common/files'; @@ -48,7 +48,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { prefersExecuteOnUI, prefersExecuteOnWorkspace, canExecuteOnUI, canExecuteOnWorkspace, prefersExecuteOnWeb } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IActionViewItemOptions, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { EXTENSIONS_CONFIG, IExtensionsConfigContent } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; @@ -59,9 +59,6 @@ import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { ILogService } from 'vs/platform/log/common/log'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { infoIcon, manageExtensionIcon, syncEnabledIcon, syncIgnoredIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; // {{SQL CARBON EDIT}} -import product from 'vs/platform/product/common/product'; -import { mnemonicButtonLabel } from 'vs/base/common/labels'; function getRelativeDateLabel(date: Date): string { const delta = new Date().getTime() - date.getTime(); @@ -2373,110 +2370,6 @@ export class EnableAllWorkspaceAction extends Action { } } -export class InstallVSIXAction extends Action { - - static readonly ID = 'workbench.extensions.action.installVSIX'; - static readonly LABEL = localize('installVSIX', "Install from VSIX..."); - static readonly AVAILABLE = !(product.disabledFeatures && product.disabledFeatures.indexOf(InstallVSIXAction.ID) >= 0); // {{SQL CARBON EDIT}} add available logic - - constructor( - id = InstallVSIXAction.ID, - label = InstallVSIXAction.LABEL, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @INotificationService private readonly notificationService: INotificationService, - @IHostService private readonly hostService: IHostService, - @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IExtensionService private readonly extensionService: IExtensionService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, // {{SQL CARBON EDIT}} - @IStorageService private storageService: IStorageService - ) { - super(id, label, 'extension-action install-vsix', true); - } - - async run(vsixPaths?: URI[]): Promise { - // {{SQL CARBON EDIT}} - Replace run body - let extensionPolicy = this.configurationService.getValue(ExtensionsPolicyKey); - if (extensionPolicy === ExtensionsPolicy.allowAll) { - if (!vsixPaths) { - vsixPaths = await this.fileDialogService.showOpenDialog({ - title: localize('installFromVSIX', "Install from VSIX"), - filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], - canSelectFiles: true, - canSelectMany: true, - openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) - }); - - if (!vsixPaths) { - return; - } - } - - await Promise.all(vsixPaths.map(async vsix => { - if (!this.storageService.getBoolean(vsix.fsPath, StorageScope.GLOBAL)) { - const accept = await new Promise(resolve => { - 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: () => resolve(true) - }, - { - label: localize('thirdPartyExt.no', 'No'), - run: () => resolve(false) - }, - { - label: localize('thirdPartyExt.dontShowAgain', 'Don\'t Show Again'), - isSecondary: true, - run: () => { - this.storageService.store(vsix.fsPath, true, StorageScope.GLOBAL, StorageTarget.MACHINE); - resolve(true); - } - } - ], - { sticky: true } - ); - }); - - if (!accept) { - return undefined; - } - } - - return this.extensionsWorkbenchService.install(vsix); - })).then(async (extensions) => { - for (const extension of extensions) { - if (!extension) { - return; - } - const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local))); - const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Azure Data Studio to complete installing the extension {0}.", extension.displayName || extension.name) // {{SQL CARBON EDIT}} - : localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.displayName || extension.name); - const actions = requireReload ? [{ - label: localize('InstallVSIXAction.reloadNow', "Reload Now"), - run: () => this.hostService.reload() - }] : []; - this.notificationService.prompt( - Severity.Info, - message, - actions, - { sticky: true } - ); - } - await this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL).run(); - }); - } else { - this.notificationService.error(localize('InstallVSIXAction.allowNone', 'Your extension policy does not allow downloading extensions. Please change your extension policy and try again.')); - } - } - - get enabled(): boolean { // {{SQL CARBON EDIT}} add enabled logic - return InstallVSIXAction.AVAILABLE; - } -} - export class ReinstallAction extends Action { static readonly ID = 'workbench.extensions.action.reinstall'; diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 446b14d98e..16da760134 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -24,7 +24,7 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat import { IJSONSchema } from 'vs/base/common/jsonSchema'; // eslint-disable-next-line code-import-patterns -import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; // {{SQL CARBON EDIT}} add import +import { SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID } from 'vs/workbench/contrib/extensions/common/extensions'; // Actions (function registerActions(): void { @@ -99,15 +99,15 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten // Menu (function registerMenu(): void { - if (InstallVSIXAction.AVAILABLE) { - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { // {{SQL CARBON EDIT}} - Add install VSIX menu item - group: '5.1_installExtension', - command: { - id: InstallVSIXAction.ID, - title: localize({ key: 'miinstallVsix', comment: ['&& denotes a mnemonic'] }, "Install Extension from VSIX Package") - } - }); - } + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { // {{SQL CARBON EDIT}} - Add install VSIX menu item + group: '5.1_installExtension', + command: { + id: SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, + title: localize({ key: 'miinstallVsix', comment: ['&& denotes a mnemonic'] }, "Install Extension from VSIX Package") + } + }); + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '6_close',