Workspace Trust Prop Changes (#121779)

* update wording for dialog

* remove soft request prompts

* tweak wording

* use origin

* clean up language for choice prompt
This commit is contained in:
SteVen Batten
2021-04-23 10:14:09 -07:00
committed by GitHub
parent ee3812f51c
commit 6c747e75da
16 changed files with 371 additions and 122 deletions

View File

@@ -38,16 +38,11 @@ export class TypeScriptVersionManager extends Disposable {
this._currentVersion = localVersion;
}
} else {
setImmediate(() => {
vscode.workspace.requestWorkspaceTrust({ modal: false })
.then(trusted => {
if (trusted && this.versionProvider.localVersion) {
this._disposables.push(vscode.workspace.onDidReceiveWorkspaceTrust(() => {
if (this.versionProvider.localVersion) {
this.updateActiveVersion(this.versionProvider.localVersion);
} else {
this.updateActiveVersion(this.versionProvider.defaultVersion);
}
});
});
}));
}
}

View File

@@ -40,3 +40,15 @@
.monaco-button-dropdown > .monaco-dropdown-button {
margin-left: 1px;
}
.monaco-description-button {
flex-direction: column;
}
.monaco-description-button .monaco-button-label {
font-weight: 500;
}
.monaco-description-button .monaco-button-description {
font-style: italic;
}

View File

@@ -50,6 +50,10 @@ export interface IButton extends IDisposable {
hasFocus(): boolean;
}
export interface IButtonWithDescription extends IButton {
description: string;
}
export class Button extends Disposable implements IButton {
private _element: HTMLElement;
@@ -303,6 +307,207 @@ export class ButtonWithDropdown extends Disposable implements IButton {
}
}
export class ButtonWithDescription extends Disposable implements IButtonWithDescription {
private _element: HTMLElement;
private _labelElement: HTMLElement;
private _descriptionElement: HTMLElement;
private options: IButtonOptions;
private buttonBackground: Color | undefined;
private buttonHoverBackground: Color | undefined;
private buttonForeground: Color | undefined;
private buttonSecondaryBackground: Color | undefined;
private buttonSecondaryHoverBackground: Color | undefined;
private buttonSecondaryForeground: Color | undefined;
private buttonBorder: Color | undefined;
private _onDidClick = this._register(new Emitter<Event>());
get onDidClick(): BaseEvent<Event> { return this._onDidClick.event; }
private focusTracker: IFocusTracker;
constructor(container: HTMLElement, options?: IButtonOptions) {
super();
this.options = options || Object.create(null);
mixin(this.options, defaultOptions, false);
this.buttonForeground = this.options.buttonForeground;
this.buttonBackground = this.options.buttonBackground;
this.buttonHoverBackground = this.options.buttonHoverBackground;
this.buttonSecondaryForeground = this.options.buttonSecondaryForeground;
this.buttonSecondaryBackground = this.options.buttonSecondaryBackground;
this.buttonSecondaryHoverBackground = this.options.buttonSecondaryHoverBackground;
this.buttonBorder = this.options.buttonBorder;
this._element = document.createElement('a');
this._element.classList.add('monaco-button');
this._element.classList.add('monaco-description-button');
this._element.tabIndex = 0;
this._element.setAttribute('role', 'button');
this._labelElement = document.createElement('div');
this._labelElement.classList.add('monaco-button-label');
this._labelElement.tabIndex = -1;
this._element.appendChild(this._labelElement);
this._descriptionElement = document.createElement('div');
this._descriptionElement.classList.add('monaco-button-description');
this._descriptionElement.tabIndex = -1;
this._element.appendChild(this._descriptionElement);
container.appendChild(this._element);
this._register(Gesture.addTarget(this._element));
[EventType.CLICK, TouchEventType.Tap].forEach(eventType => {
this._register(addDisposableListener(this._element, eventType, e => {
if (!this.enabled) {
EventHelper.stop(e);
return;
}
this._onDidClick.fire(e);
}));
});
this._register(addDisposableListener(this._element, EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
let eventHandled = false;
if (this.enabled && (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) {
this._onDidClick.fire(e);
eventHandled = true;
} else if (event.equals(KeyCode.Escape)) {
this._element.blur();
eventHandled = true;
}
if (eventHandled) {
EventHelper.stop(event, true);
}
}));
this._register(addDisposableListener(this._element, EventType.MOUSE_OVER, e => {
if (!this._element.classList.contains('disabled')) {
this.setHoverBackground();
}
}));
this._register(addDisposableListener(this._element, EventType.MOUSE_OUT, e => {
this.applyStyles(); // restore standard styles
}));
// Also set hover background when button is focused for feedback
this.focusTracker = this._register(trackFocus(this._element));
this._register(this.focusTracker.onDidFocus(() => this.setHoverBackground()));
this._register(this.focusTracker.onDidBlur(() => this.applyStyles())); // restore standard styles
this.applyStyles();
}
private setHoverBackground(): void {
let hoverBackground;
if (this.options.secondary) {
hoverBackground = this.buttonSecondaryHoverBackground ? this.buttonSecondaryHoverBackground.toString() : null;
} else {
hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
}
if (hoverBackground) {
this._element.style.backgroundColor = hoverBackground;
}
}
style(styles: IButtonStyles): void {
this.buttonForeground = styles.buttonForeground;
this.buttonBackground = styles.buttonBackground;
this.buttonHoverBackground = styles.buttonHoverBackground;
this.buttonSecondaryForeground = styles.buttonSecondaryForeground;
this.buttonSecondaryBackground = styles.buttonSecondaryBackground;
this.buttonSecondaryHoverBackground = styles.buttonSecondaryHoverBackground;
this.buttonBorder = styles.buttonBorder;
this.applyStyles();
}
private applyStyles(): void {
if (this._element) {
let background, foreground;
if (this.options.secondary) {
foreground = this.buttonSecondaryForeground ? this.buttonSecondaryForeground.toString() : '';
background = this.buttonSecondaryBackground ? this.buttonSecondaryBackground.toString() : '';
} else {
foreground = this.buttonForeground ? this.buttonForeground.toString() : '';
background = this.buttonBackground ? this.buttonBackground.toString() : '';
}
const border = this.buttonBorder ? this.buttonBorder.toString() : '';
this._element.style.color = foreground;
this._element.style.backgroundColor = background;
this._element.style.borderWidth = border ? '1px' : '';
this._element.style.borderStyle = border ? 'solid' : '';
this._element.style.borderColor = border;
}
}
get element(): HTMLElement {
return this._element;
}
set label(value: string) {
this._element.classList.add('monaco-text-button');
if (this.options.supportIcons) {
reset(this._labelElement, ...renderLabelWithIcons(value));
} else {
this._labelElement.textContent = value;
}
if (typeof this.options.title === 'string') {
this._element.title = this.options.title;
} else if (this.options.title) {
this._element.title = value;
}
}
set description(value: string) {
if (this.options.supportIcons) {
reset(this._descriptionElement, ...renderLabelWithIcons(value));
} else {
this._descriptionElement.textContent = value;
}
}
set icon(icon: CSSIcon) {
this._element.classList.add(...CSSIcon.asClassNameArray(icon));
}
set enabled(value: boolean) {
if (value) {
this._element.classList.remove('disabled');
this._element.setAttribute('aria-disabled', String(false));
this._element.tabIndex = 0;
} else {
this._element.classList.add('disabled');
this._element.setAttribute('aria-disabled', String(true));
}
}
get enabled() {
return !this._element.classList.contains('disabled');
}
focus(): void {
this._element.focus();
}
hasFocus(): boolean {
return this._element === document.activeElement;
}
}
export class ButtonBar extends Disposable {
private _buttons: IButton[] = [];
@@ -321,6 +526,12 @@ export class ButtonBar extends Disposable {
return button;
}
addButtonWithDescription(options?: IButtonOptions): IButtonWithDescription {
const button = this._register(new ButtonWithDescription(this.container, options));
this.pushButton(button);
return button;
}
addButtonWithDropdown(options: IButtonWithDropdownOptions): IButton {
const button = this._register(new ButtonWithDropdown(this.container, options));
this.pushButton(button);

View File

@@ -34,6 +34,7 @@
/** Dialog: Title Actions Row */
.monaco-dialog-box .dialog-toolbar-row {
height: 22px;
padding-bottom: 4px;
}
@@ -138,6 +139,10 @@
overflow: hidden;
}
.monaco-dialog-box > .dialog-buttons-row > .dialog-buttons.centered {
justify-content: center;
}
.monaco-dialog-box > .dialog-buttons-row > .dialog-buttons > .monaco-button {
width: fit-content;
width: -moz-fit-content;

View File

@@ -11,7 +11,7 @@ import { domEvent } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Color } from 'vs/base/common/color';
import { ButtonBar, IButtonStyles } from 'vs/base/browser/ui/button/button';
import { ButtonBar, ButtonWithDescription, IButtonStyles } from 'vs/base/browser/ui/button/button';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
@@ -36,6 +36,8 @@ export interface IDialogOptions {
readonly keyEventProcessor?: (event: StandardKeyboardEvent) => void;
readonly renderBody?: (container: HTMLElement) => void;
readonly icon?: Codicon;
readonly buttonDetails?: string[];
readonly disableCloseAction?: boolean;
}
export interface IDialogResult {
@@ -55,6 +57,8 @@ export interface IDialogStyles extends IButtonStyles, ISimpleCheckboxStyles {
readonly inputBackground?: Color;
readonly inputForeground?: Color;
readonly inputBorder?: Color;
readonly textLinkForeground?: Color;
}
interface ButtonMapEntry {
@@ -73,6 +77,7 @@ export class Dialog extends Disposable {
private modalElement: HTMLElement | undefined;
private readonly buttonsContainer: HTMLElement;
private readonly messageDetailElement: HTMLElement;
private readonly messageContainer: HTMLElement;
private readonly iconElement: HTMLElement;
private readonly checkbox: SimpleCheckbox | undefined;
private readonly toolbarContainer: HTMLElement;
@@ -97,15 +102,15 @@ export class Dialog extends Disposable {
const messageRowElement = this.element.appendChild($('.dialog-message-row'));
this.iconElement = messageRowElement.appendChild($('.dialog-icon'));
const messageContainer = messageRowElement.appendChild($('.dialog-message-container'));
this.messageContainer = messageRowElement.appendChild($('.dialog-message-container'));
if (this.options.detail || this.options.renderBody) {
const messageElement = messageContainer.appendChild($('.dialog-message'));
const messageElement = this.messageContainer.appendChild($('.dialog-message'));
const messageTextElement = messageElement.appendChild($('.dialog-message-text'));
messageTextElement.innerText = this.message;
}
this.messageDetailElement = messageContainer.appendChild($('.dialog-message-detail'));
this.messageDetailElement = this.messageContainer.appendChild($('.dialog-message-detail'));
if (this.options.detail || !this.options.renderBody) {
this.messageDetailElement.innerText = this.options.detail ? this.options.detail : message;
} else {
@@ -113,13 +118,13 @@ export class Dialog extends Disposable {
}
if (this.options.renderBody) {
const customBody = messageContainer.appendChild($('.dialog-message-body'));
const customBody = this.messageContainer.appendChild($('.dialog-message-body'));
this.options.renderBody(customBody);
}
if (this.options.inputs) {
this.inputs = this.options.inputs.map(input => {
const inputRowElement = messageContainer.appendChild($('.dialog-message-input'));
const inputRowElement = this.messageContainer.appendChild($('.dialog-message-input'));
const inputBox = this._register(new InputBox(inputRowElement, undefined, {
placeholder: input.placeholder,
@@ -137,7 +142,7 @@ export class Dialog extends Disposable {
}
if (this.options.checkboxLabel) {
const checkboxRowElement = messageContainer.appendChild($('.dialog-checkbox-row'));
const checkboxRowElement = this.messageContainer.appendChild($('.dialog-checkbox-row'));
const checkbox = this.checkbox = this._register(new SimpleCheckbox(this.options.checkboxLabel, !!this.options.checkboxChecked));
@@ -186,12 +191,16 @@ export class Dialog extends Disposable {
const buttonBar = this.buttonBar = this._register(new ButtonBar(this.buttonsContainer));
const buttonMap = this.rearrangeButtons(this.buttons, this.options.cancelId);
this.buttonsContainer.classList.toggle('centered');
// Handle button clicks
buttonMap.forEach((entry, index) => {
const button = this._register(buttonBar.addButton({ title: true }));
const primary = buttonMap[index].index === 0;
const button = this.options.buttonDetails ? this._register(buttonBar.addButtonWithDescription({ title: true, secondary: !primary })) : this._register(buttonBar.addButton({ title: true, secondary: !primary }));
button.label = mnemonicButtonLabel(buttonMap[index].label, true);
if (button instanceof ButtonWithDescription) {
button.description = this.options.buttonDetails![buttonMap[index].index];
}
this._register(button.onDidClick(e => {
if (e) {
EventHelper.stop(e);
@@ -298,7 +307,7 @@ export class Dialog extends Disposable {
EventHelper.stop(e, true);
const evt = new StandardKeyboardEvent(e);
if (evt.equals(KeyCode.Escape)) {
if (!this.options.disableCloseAction && evt.equals(KeyCode.Escape)) {
resolve({
button: this.options.cancelId || 0,
checkboxChecked: this.checkbox ? this.checkbox.checked : undefined
@@ -346,6 +355,8 @@ export class Dialog extends Disposable {
}
}
if (!this.options.disableCloseAction) {
const actionBar = this._register(new ActionBar(this.toolbarContainer, {}));
const action = this._register(new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), dialogCloseIcon.classNames, true, async () => {
@@ -356,6 +367,7 @@ export class Dialog extends Disposable {
}));
actionBar.push(action, { icon: true, label: false, });
}
this.applyStyles();
@@ -384,6 +396,7 @@ export class Dialog extends Disposable {
const bgColor = style.dialogBackground;
const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : '';
const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : '';
const linkFgColor = style.textLinkForeground;
this.shadowElement.style.boxShadow = shadowColor;
@@ -404,6 +417,12 @@ export class Dialog extends Disposable {
this.messageDetailElement.style.color = messageDetailColor.makeOpaque(bgColor).toString();
}
if (linkFgColor) {
for (const el of this.messageContainer.getElementsByTagName('a')) {
el.style.color = linkFgColor.toString();
}
}
let color;
switch (this.options.type) {
case 'error':

View File

@@ -181,9 +181,11 @@ export interface IOpenDialogOptions {
export const IDialogService = createDecorator<IDialogService>('dialogService');
export interface ICustomDialogOptions {
markdownDetails?: ICustomDialogMarkdown[]
buttonDetails?: string[];
markdownDetails?: ICustomDialogMarkdown[];
classes?: string[];
icon?: Codicon;
disableCloseAction?: boolean;
}
export interface ICustomDialogMarkdown {

View File

@@ -324,7 +324,7 @@ export function attachMenuStyler(widget: IThemable, themeService: IThemeService,
return attachStyler(themeService, { ...defaultMenuStyles, ...style }, widget);
}
export interface IDialogStyleOverrides extends IButtonStyleOverrides {
export interface IDialogStyleOverrides extends IButtonStyleOverrides, ILinkStyleOverrides {
dialogForeground?: ColorIdentifier;
dialogBackground?: ColorIdentifier;
dialogShadow?: ColorIdentifier;
@@ -347,6 +347,8 @@ export const defaultDialogStyles = <IDialogStyleOverrides>{
dialogBorder: contrastBorder,
buttonForeground: buttonForeground,
buttonBackground: buttonBackground,
buttonSecondaryBackground: buttonSecondaryBackground,
buttonSecondaryForeground: buttonSecondaryForeground,
buttonHoverBackground: buttonHoverBackground,
buttonBorder: buttonBorder,
checkboxBorder: simpleCheckboxBorder,
@@ -357,7 +359,8 @@ export const defaultDialogStyles = <IDialogStyleOverrides>{
infoIconForeground: problemsInfoIconForeground,
inputBackground: inputBackground,
inputForeground: inputForeground,
inputBorder: inputBorder
inputBorder: inputBorder,
textLinkForeground: textLinkForeground
};

View File

@@ -17,7 +17,7 @@ export function workspaceTrustToString(trustState: boolean) {
if (trustState) {
return localize('trusted', "Trusted");
} else {
return localize('untrusted', "Untrusted");
return localize('untrusted', "Restricted Mode");
}
}

View File

@@ -113,6 +113,8 @@ export class BrowserDialogHandler implements IDialogHandler {
},
renderBody,
icon: customOptions?.icon,
disableCloseAction: customOptions?.disableCloseAction,
buttonDetails: customOptions?.buttonDetails,
checkboxLabel: checkbox?.label,
checkboxChecked: checkbox?.checked,
inputs

View File

@@ -80,7 +80,7 @@ import { isWorkspaceFolder, TaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, TaskQui
import { ILogService } from 'vs/platform/log/common/log';
import { once } from 'vs/base/common/functional';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { VirtualWorkspaceContext } from 'vs/workbench/browser/contextkeys';
const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history';
@@ -257,6 +257,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
@IPreferencesService private readonly preferencesService: IPreferencesService,
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
@ILogService private readonly logService: ILogService
) {
super();
@@ -931,7 +932,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}).then((value) => {
if (runSource === TaskRunSource.User) {
this.getWorkspaceTasks().then(workspaceTasks => {
RunAutomaticTasks.promptForPermission(this, this.storageService, this.notificationService, this.workspaceTrustRequestService, this.openerService, workspaceTasks);
RunAutomaticTasks.promptForPermission(this, this.storageService, this.notificationService, this.workspaceTrustManagementService, this.openerService, workspaceTasks);
});
}
return value;

View File

@@ -115,9 +115,9 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
return { tasks, taskNames, locations };
}
public static async promptForPermission(taskService: ITaskService, storageService: IStorageService, notificationService: INotificationService, workspaceTrustRequestService: IWorkspaceTrustRequestService,
public static async promptForPermission(taskService: ITaskService, storageService: IStorageService, notificationService: INotificationService, workspaceTrustManagementService: IWorkspaceTrustManagementService,
openerService: IOpenerService, workspaceTaskResult: Map<string, WorkspaceFolderTaskResult>) {
const isWorkspaceTrusted = await workspaceTrustRequestService.requestWorkspaceTrust({ modal: false });
const isWorkspaceTrusted = workspaceTrustManagementService.isWorkpaceTrusted;
if (!isWorkspaceTrusted) {
return;
}

View File

@@ -41,7 +41,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
interface WorkspaceFolderConfigurationResult {
@@ -81,6 +81,7 @@ export class TaskService extends AbstractTaskService {
@IPreferencesService preferencesService: IPreferencesService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IWorkspaceTrustRequestService workspaceTrustRequestService: IWorkspaceTrustRequestService,
@IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService,
@ILogService logService: ILogService) {
super(configurationService,
markerService,
@@ -111,6 +112,7 @@ export class TaskService extends AbstractTaskService {
preferencesService,
viewDescriptorService,
workspaceTrustRequestService,
workspaceTrustManagementService,
logService);
this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(), 'veto.tasks')));
}

View File

@@ -30,20 +30,20 @@ import { EditorInput, Extensions as EditorInputExtensions, IEditorInputSerialize
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { isWeb } from 'vs/base/common/platform';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { dirname, resolve } from 'vs/base/common/path';
import product from 'vs/platform/product/common/product';
import { FileAccess } from 'vs/base/common/network';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import product from 'vs/platform/product/common/product';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { Schemas } from 'vs/base/common/network';
import { splitName } from 'vs/base/common/labels';
/*
* Trust Request UX Handler
*/
export class WorkspaceTrustRequestHandler extends Disposable implements IWorkbenchContribution {
private shouldShowIntroduction = true;
constructor(
@IDialogService private readonly dialogService: IDialogService,
@@ -52,75 +52,86 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
@IStorageService private readonly storageService: IStorageService,
) {
super();
if (isWorkspaceTrustEnabled(configurationService)) {
this.registerListeners();
this.showIntroductionModal();
this.showModalOnStart();
}
}
private showIntroductionModal(): void {
const workspaceTrustIntroDialogDoNotShowAgainKey = 'workspace.trust.introduction.doNotShowAgain';
const doNotShowAgain = this.storageService.getBoolean(workspaceTrustIntroDialogDoNotShowAgainKey, StorageScope.GLOBAL, false);
if (!doNotShowAgain && this.shouldShowIntroduction) {
// Show welcome dialog
(async () => {
private async doShowModal(question: string, trustedOption: { label: string, sublabel: string }, untrustedOption: { label: string, sublabel: string }, markdownStrings: string[], trustParentString?: string): Promise<void> {
const result = await this.dialogService.show(
Severity.Info,
localize('workspaceTrust', "Introducing Workspace Trust"),
question,
[
localize('manageTrust', "Manage"),
localize('close', "Close")
trustedOption.label,
untrustedOption.label,
],
{
checkbox: trustParentString ? {
label: trustParentString
} : undefined,
custom: {
icon: Codicon.shield,
classes: ['workspace-trust-intro-dialog'],
markdownDetails: [
{
markdown: new MarkdownString(localize('workspaceTrustDescription', "{0} provides many powerful features that rely on the files that are open in the current workspace. This can mean unintended code execution from the workspace and should only happen if you trust the source of the files you have open.", 'VS Code' || product.nameShort)),
},
{
markdown: new MarkdownString(`![${localize('altTextTrustedBadge', "Shield Badge on Activity Bar")}](${FileAccess.asBrowserUri('vs/workbench/contrib/workspace/browser/media/trusted-badge.png', require).toString(false)})\n*${localize('workspaceTrustBadgeDescription', "When features are disabled in an untrusted workspace, you will see this shield icon in the Activity Bar.")}*`),
classes: ['workspace-trust-dialog-image-row', 'badge-row']
},
{
markdown: new MarkdownString(`![${localize('altTextUntrustedStatus', "Workspace Trust Status Bar Entry")}](${FileAccess.asBrowserUri('vs/workbench/contrib/workspace/browser/media/untrusted-status.png', require).toString(false)})\n*${localize('workspaceTrustUntrustedDescription', "When the workspace is untrusted, you will see this status bar entry. It is hidden when the workspace is trusted.")}*`),
classes: ['workspace-trust-dialog-image-row', 'status-bar']
},
{
markdown: new MarkdownString(localize('seeTheDocs', "Manage your Workspace Trust configuration now to learn more or [see our docs for additional information](https://aka.ms/vscode-workspace-trust).")),
}
buttonDetails: [
trustedOption.sublabel,
untrustedOption.sublabel
],
disableCloseAction: true,
icon: Codicon.shield,
markdownDetails: markdownStrings.map(md => { return { markdown: new MarkdownString(md) }; })
},
cancelId: 1, // Close
checkbox: {
label: localize('dontShowAgain', "Don't show this message again"),
checked: false,
}
}
);
// Dialog result
switch (result.choice) {
case 0:
this.workspaceTrustRequestService.cancelRequest();
await this.commandService.executeCommand('workbench.trust.manage');
if (result.checkboxChecked) {
this.workspaceTrustManagementService.setParentFolderTrust(true);
} else {
this.workspaceTrustRequestService.completeRequest(true);
}
break;
case 1:
this.workspaceTrustRequestService.completeRequest(undefined);
this.workspaceTrustRequestService.cancelRequest();
break;
}
if (result.checkboxChecked) {
this.storageService.store(workspaceTrustIntroDialogDoNotShowAgainKey, true, StorageScope.GLOBAL, StorageTarget.USER);
await this.commandService.executeCommand('workbench.trust.manage');
}
})();
private showModalOnStart(): void {
if (this.workspaceTrustManagementService.isWorkpaceTrusted()) {
return;
}
let checkboxText: string | undefined;
const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceContextService.getWorkspace())!;
const isSingleFolderWorkspace = isSingleFolderWorkspaceIdentifier(workspaceIdentifier);
if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file) {
const { parentPath } = splitName(workspaceIdentifier.uri.fsPath);
const { name } = splitName(parentPath);
checkboxText = localize('checkboxString', "I trust the author of all files in '{0}'", name);
}
// Show Workspace Trust Start Dialog
this.doShowModal(
!isSingleFolderWorkspace ?
localize('workspaceTrust', "Do you trust the authors of the files in this workspace?") :
localize('folderTrust', "Do you trust the authors of the files in this folder?"),
{ label: localize('trustOption', "Yes, I trust the authors"), sublabel: isSingleFolderWorkspace ? localize('trustFolderOptionDescription', "Trust folder and enable all features") : localize('trustWorkspaceOptionDescription', "Trust workspace and enable all features") },
{ label: localize('dontTrustOption', "No, I don't trust the authors"), sublabel: isSingleFolderWorkspace ? localize('dontTrustFolderOptionDescription', "Browse folder in restricted mode") : localize('dontTrustWorkspaceOptionDescription', "Browse workspace in restricted mode") },
[
!isSingleFolderWorkspace ?
localize('workspaceStartupTrustDetails', "{0} provides advanced editing features that may automatically execute files in this workspace.", product.nameShort) :
localize('folderStartupTrustDetails', "{0} provides advanced editing features that may automatically execute files in this folder.", product.nameShort),
localize('learnMore', "If you don't trust the author of these files we recommend to continue in restricted mode as the files may be malicious. See [our docs](https://aka.ms/vscode-workspace-trust) to learn more.")
],
checkboxText
);
}
private registerListeners(): void {
@@ -206,9 +217,6 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
resolve();
}));
}));
// Don't auto-show the UX editor if the request is 5 seconds after startup
setTimeout(() => { this.shouldShowIntroduction = false; }, 5000);
}
}

View File

@@ -26,6 +26,7 @@ import { ExtensionUntrustedWorkpaceSupportType } from 'vs/platform/extensions/co
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IPromptChoiceWithMenu, Severity } from 'vs/platform/notification/common/notification';
import { Link } from 'vs/platform/opener/browser/link';
import product from 'vs/platform/product/common/product';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { foreground } from 'vs/platform/theme/common/colorRegistry';
@@ -154,7 +155,7 @@ export class WorkspaceTrustEditor extends EditorPane {
return localize('trustedDescription', "All features are enabled because trust has been granted to the workspace. [Learn more](https://aka.ms/vscode-workspace-trust).");
}
return localize('untrustedDescription', "Some features are disabled until trust is granted to the workspace. [Learn more](https://aka.ms/vscode-workspace-trust).");
return localize('untrustedDescription', "{0} is in a restricted mode optimized for browsing code safely. [Learn more](https://aka.ms/vscode-workspace-trust).", product.nameShort);
}
private getHeaderTitleIconClassNames(trusted: boolean): string[] {
@@ -271,7 +272,7 @@ export class WorkspaceTrustEditor extends EditorPane {
], checkListIcon.classNamesArray);
const untrustedContainer = append(this.affectedFeaturesContainer, $('.workspace-trust-limitations.untrusted'));
this.renderLimitationsHeaderElement(untrustedContainer, localize('untrustedWorkspace', "Untrusted Workspace"), this.getHeaderTitleIconClassNames(false));
this.renderLimitationsHeaderElement(untrustedContainer, localize('untrustedWorkspace', "Restricted Mode"), this.getHeaderTitleIconClassNames(false));
this.renderLimitationsListElement(untrustedContainer, [
localize('untrustedTasks', "Tasks will be disabled"),

View File

@@ -14,7 +14,7 @@ import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, I
import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels';
import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString } from 'vs/platform/configuration/common/configuration';
import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels';
import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, UntrustedSettings, filterSettingsRequireWorkspaceTrust } from 'vs/workbench/services/configuration/common/configuration';
import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, UntrustedSettings } from 'vs/workbench/services/configuration/common/configuration';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IWorkspaceInitializationPayload, IEmptyWorkspaceIdentifier, useSlashForPath, getStoredWorkspaceFolder, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces';
@@ -32,7 +32,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
import { ILogService } from 'vs/platform/log/common/log';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { delta, distinct } from 'vs/base/common/arrays';
import { forEach, IStringDictionary } from 'vs/base/common/collections';
@@ -955,24 +955,6 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
}
}
class ConfigurationWorkspaceTrustContribution extends Disposable implements IWorkbenchContribution {
constructor(
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
@IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService
) {
super();
this.requestTrust();
this._register(configurationService.onDidChangeUntrustdSettings(() => this.requestTrust()));
}
private requestTrust(): void {
const settingsRequiringWorkspaceTrust = filterSettingsRequireWorkspaceTrust(this.configurationService.unTrustedSettings.default);
if (settingsRequiringWorkspaceTrust.length) {
this.workspaceTrustRequestService.requestWorkspaceTrust();
}
}
}
class RegisterConfigurationSchemasContribution extends Disposable implements IWorkbenchContribution {
constructor(
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@@ -1095,4 +1077,3 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored);
workbenchContributionsRegistry.registerWorkbenchContribution(ConfigurationWorkspaceTrustContribution, LifecyclePhase.Starting);

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { splitName } from 'vs/base/common/labels';
import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { dirname } from 'vs/base/common/resources';
@@ -212,6 +213,12 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
}
setParentFolderTrust(trusted: boolean): void {
const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceService.getWorkspace());
if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file) {
const { parentPath } = splitName(workspaceIdentifier.uri.fsPath);
this.setFoldersTrust([URI.file(parentPath)], trusted);
}
}
setWorkspaceTrust(trusted: boolean): void {