Debt: move open file trust request dialog into the contribution (#126062)

This commit is contained in:
Ladislau Szomoru 2021-06-14 18:31:25 +02:00 committed by GitHub
parent 8440af2409
commit 18caeb21fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 160 additions and 105 deletions

View file

@ -74,11 +74,14 @@ export const IWorkspaceTrustRequestService = createDecorator<IWorkspaceTrustRequ
export interface IWorkspaceTrustRequestService {
readonly _serviceBrand: undefined;
readonly onDidInitiateOpenFilesTrustRequest: Event<void>;
readonly onDidInitiateWorkspaceTrustRequest: Event<WorkspaceTrustRequestOptions | undefined>;
requestOpenUris(uris: URI[]): Promise<WorkspaceTrustUriResponse>;
cancelRequest(): void;
completeRequest(trusted?: boolean): Promise<void>;
completeOpenFilesTrustRequest(result: WorkspaceTrustUriResponse, saveResponse?: boolean): Promise<void>;
requestOpenFilesTrust(openFiles: URI[]): Promise<WorkspaceTrustUriResponse>;
cancelWorkspaceTrustRequest(): void;
completeWorkspaceTrustRequest(trusted?: boolean): Promise<void>;
requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<boolean | undefined>;
}

View file

@ -13,7 +13,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { Severity } from 'vs/platform/notification/common/notification';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, workspaceTrustToString } from 'vs/platform/workspace/common/workspaceTrust';
import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, workspaceTrustToString, WorkspaceTrustUriResponse } from 'vs/platform/workspace/common/workspaceTrust';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Codicon } from 'vs/base/common/codicons';
@ -71,15 +71,56 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
return !isSingleFolderWorkspaceIdentifier(toWorkspaceIdentifier(this.workspaceContextService.getWorkspace()));
}
private get modalTitle(): string {
return this.useWorkspaceLanguage ?
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?");
}
private async registerListeners(): Promise<void> {
await this.workspaceTrustManagementService.workspaceResolved;
// Open files trust request
this._register(this.workspaceTrustRequestService.onDidInitiateOpenFilesTrustRequest(async () => {
// Details
const markdownDetails = [
this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY ?
localize('openLooseFileWorkspaceDetails', "You are trying to open untrusted files in a workspace which is trusted.") :
localize('openLooseFileWindowDetails', "You are trying to open untrusted files in a window which is trusted."),
localize('openLooseFileLearnMore', "If you don't trust the authors of these files, we recommend to open them in Restricted Mode in a new window as the files may be malicious. See [our docs](https://aka.ms/vscode-workspace-trust) to learn more.")
];
// Dialog
const result = await this.dialogService.show(
Severity.Info,
localize('openLooseFileMesssage', "Do you trust the authors of these files?"),
[localize('open', "Open"), localize('newWindow', "Open in Restricted Mode"), localize('cancel', "Cancel")],
{
cancelId: 2,
checkbox: {
label: localize('openLooseFileWorkspaceCheckbox', "Remember my decision for all workspaces"),
checked: false
},
custom: {
icon: Codicon.shield,
markdownDetails: markdownDetails.map(md => { return { markdown: new MarkdownString(md) }; })
}
});
switch (result.choice) {
case 0:
await this.workspaceTrustRequestService.completeOpenFilesTrustRequest(WorkspaceTrustUriResponse.Open, !!result.checkboxChecked);
break;
case 1:
await this.workspaceTrustRequestService.completeOpenFilesTrustRequest(WorkspaceTrustUriResponse.OpenInNewWindow, !!result.checkboxChecked);
break;
default:
await this.workspaceTrustRequestService.completeOpenFilesTrustRequest(WorkspaceTrustUriResponse.Cancel);
break;
}
}));
// Workspace trust request
this._register(this.workspaceTrustRequestService.onDidInitiateWorkspaceTrustRequest(async requestOptions => {
// Title
const title = this.useWorkspaceLanguage ?
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?");
// Message
const defaultMessage = localize('immediateTrustRequestMessage', "A feature you are trying to use may be a security risk if you do not trust the source of the files or folders you currently have open.");
const message = requestOptions?.message ?? defaultMessage;
@ -89,6 +130,7 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
{ label: this.useWorkspaceLanguage ? localize('grantWorkspaceTrustButton', "Trust Workspace & Continue") : localize('grantFolderTrustButton', "Trust Folder & Continue"), type: 'ContinueWithTrust' },
{ label: localize('manageWorkspaceTrustButton', "Manage"), type: 'Manage' }
];
// Add Cancel button if not provided
if (!buttons.some(b => b.type === 'Cancel')) {
buttons.push({ label: localize('cancelWorkspaceTrustButton', "Cancel"), type: 'Cancel' });
@ -97,7 +139,7 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
// Dialog
const result = await this.dialogService.show(
Severity.Info,
this.modalTitle,
title,
buttons.map(b => b.label),
{
cancelId: buttons.findIndex(b => b.type === 'Cancel'),
@ -114,17 +156,17 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
// Dialog result
switch (buttons[result.choice].type) {
case 'ContinueWithTrust':
await this.workspaceTrustRequestService.completeRequest(true);
await this.workspaceTrustRequestService.completeWorkspaceTrustRequest(true);
break;
case 'ContinueWithoutTrust':
await this.workspaceTrustRequestService.completeRequest(undefined);
await this.workspaceTrustRequestService.completeWorkspaceTrustRequest(undefined);
break;
case 'Manage':
this.workspaceTrustRequestService.cancelRequest();
this.workspaceTrustRequestService.cancelWorkspaceTrustRequest();
await this.commandService.executeCommand(MANAGE_TRUST_COMMAND_ID);
break;
case 'Cancel':
this.workspaceTrustRequestService.cancelRequest();
this.workspaceTrustRequestService.cancelWorkspaceTrustRequest();
break;
}
}));
@ -223,12 +265,12 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon
if (result.checkboxChecked) {
await this.workspaceTrustManagementService.setParentFolderTrust(true);
} else {
await this.workspaceTrustRequestService.completeRequest(true);
await this.workspaceTrustRequestService.completeWorkspaceTrustRequest(true);
}
break;
case 1:
this.updateWorkbenchIndicators(false);
this.workspaceTrustRequestService.cancelRequest();
this.workspaceTrustRequestService.cancelWorkspaceTrustRequest();
break;
}

View file

@ -760,7 +760,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
private async handleWorkspaceTrust(editors: Array<IEditorInputWithOptions | IResourceEditorInputType>): Promise<boolean> {
const { resources, diffMode } = this.extractEditorResources(editors);
const trustResult = await this.workspaceTrustRequestService.requestOpenUris(resources);
const trustResult = await this.workspaceTrustRequestService.requestOpenFilesTrust(resources);
switch (trustResult) {
case WorkspaceTrustUriResponse.Open:
return true;

View file

@ -3,19 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { Emitter } from 'vs/base/common/event';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { splitName } from 'vs/base/common/labels';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { Schemas } from 'vs/base/common/network';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { isVirtualResource } from 'vs/platform/remote/common/remoteHosts';
@ -577,20 +573,25 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa
_serviceBrand: undefined;
private _trusted!: boolean;
private _modalTrustRequestPromise?: Promise<boolean | undefined>;
private _modalTrustRequestResolver?: (trusted: boolean | undefined) => void;
private readonly _ctxWorkspaceTrustEnabled: IContextKey<boolean>;
private readonly _ctxWorkspaceTrustState: IContextKey<boolean>;
private _openFilesTrustRequestPromise?: Promise<WorkspaceTrustUriResponse>;
private _openFilesTrustRequestResolver?: (response: WorkspaceTrustUriResponse) => void;
private _workspaceTrustRequestPromise?: Promise<boolean | undefined>;
private _workspaceTrustRequestResolver?: (trusted: boolean | undefined) => void;
private readonly _onDidInitiateOpenFilesTrustRequest = this._register(new Emitter<void>());
readonly onDidInitiateOpenFilesTrustRequest = this._onDidInitiateOpenFilesTrustRequest.event;
private readonly _onDidInitiateWorkspaceTrustRequest = this._register(new Emitter<WorkspaceTrustRequestOptions | undefined>());
readonly onDidInitiateWorkspaceTrustRequest = this._onDidInitiateWorkspaceTrustRequest.event;
private readonly _ctxWorkspaceTrustEnabled: IContextKey<boolean>;
private readonly _ctxWorkspaceTrustState: IContextKey<boolean>;
constructor(
@IContextKeyService contextKeyService: IContextKeyService,
@IDialogService private readonly dialogService: IDialogService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService
) {
super();
@ -613,6 +614,8 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa
this._ctxWorkspaceTrustState.set(trusted);
}
//#region Open file(s) trust request
private get untrustedFilesSetting(): 'prompt' | 'open' | 'newWindow' {
return this.configurationService.getValue(WORKSPACE_TRUST_UNTRUSTED_FILES);
}
@ -621,36 +624,35 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa
this.configurationService.updateValue(WORKSPACE_TRUST_UNTRUSTED_FILES, value);
}
private resolveRequest(trusted?: boolean): void {
if (this._modalTrustRequestResolver) {
this._modalTrustRequestResolver(trusted ?? this.trusted);
this._modalTrustRequestResolver = undefined;
this._modalTrustRequestPromise = undefined;
}
}
cancelRequest(): void {
if (this._modalTrustRequestResolver) {
this._modalTrustRequestResolver(undefined);
this._modalTrustRequestResolver = undefined;
this._modalTrustRequestPromise = undefined;
}
}
async completeRequest(trusted?: boolean): Promise<void> {
if (trusted === undefined || trusted === this.trusted) {
this.resolveRequest(trusted);
async completeOpenFilesTrustRequest(result: WorkspaceTrustUriResponse, saveResponse?: boolean): Promise<void> {
if (!this._openFilesTrustRequestResolver) {
return;
}
// Update storage, transition workspace, and resolve the promise
await this.workspaceTrustManagementService.setWorkspaceTrust(trusted);
this.resolveRequest(trusted);
// Set acceptsOutOfWorkspaceFiles
if (result === WorkspaceTrustUriResponse.Open) {
this.workspaceTrustManagementService.acceptsOutOfWorkspaceFiles = true;
}
// Save response
if (saveResponse) {
if (result === WorkspaceTrustUriResponse.Open) {
this.untrustedFilesSetting = 'open';
}
if (result === WorkspaceTrustUriResponse.OpenInNewWindow) {
this.untrustedFilesSetting = 'newWindow';
}
}
// Resolve promise
this._openFilesTrustRequestResolver(result);
this._openFilesTrustRequestResolver = undefined;
this._openFilesTrustRequestPromise = undefined;
}
async requestOpenUris(uris: URI[]): Promise<WorkspaceTrustUriResponse> {
async requestOpenFilesTrust(uris: URI[]): Promise<WorkspaceTrustUriResponse> {
// If workspace is untrusted, there is no conflict
if (!this.trusted) {
return WorkspaceTrustUriResponse.Open;
@ -679,48 +681,50 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa
return WorkspaceTrustUriResponse.Open;
}
const markdownDetails = [
this.workspaceService.getWorkbenchState() !== WorkbenchState.EMPTY ?
localize('openLooseFileWorkspaceDetails', "You are trying to open untrusted files in a workspace which is trusted.") :
localize('openLooseFileWindowDetails', "You are trying to open untrusted files in a window which is trusted."),
localize('openLooseFileLearnMore', "If you don't trust the authors of these files, we recommend to open them in Restricted Mode in a new window as the files may be malicious. See [our docs](https://aka.ms/vscode-workspace-trust) to learn more.")
];
const result = await this.dialogService.show(Severity.Info, localize('openLooseFileMesssage', "Do you trust the authors of these files?"), [localize('open', "Open"), localize('newWindow', "Open in Restricted Mode"), localize('cancel', "Cancel")], {
cancelId: 2,
checkbox: {
label: localize('openLooseFileWorkspaceCheckbox', "Remember my decision for all workspaces"),
checked: false
},
custom: {
icon: Codicon.shield,
markdownDetails: markdownDetails.map(md => { return { markdown: new MarkdownString(md) }; })
}
});
const saveResponseIfChecked = (response: WorkspaceTrustUriResponse, checked: boolean) => {
if (checked) {
if (response === WorkspaceTrustUriResponse.Open) {
this.untrustedFilesSetting = 'open';
}
if (response === WorkspaceTrustUriResponse.OpenInNewWindow) {
this.untrustedFilesSetting = 'newWindow';
}
}
return response;
};
switch (result.choice) {
case 0:
this.workspaceTrustManagementService.acceptsOutOfWorkspaceFiles = true;
return saveResponseIfChecked(WorkspaceTrustUriResponse.Open, !!result.checkboxChecked);
case 1:
return saveResponseIfChecked(WorkspaceTrustUriResponse.OpenInNewWindow, !!result.checkboxChecked);
default:
return WorkspaceTrustUriResponse.Cancel;
// Create/return a promise
if (!this._openFilesTrustRequestPromise) {
this._openFilesTrustRequestPromise = new Promise<WorkspaceTrustUriResponse>(resolve => {
this._openFilesTrustRequestResolver = resolve;
});
} else {
return this._openFilesTrustRequestPromise;
}
this._onDidInitiateOpenFilesTrustRequest.fire();
return this._openFilesTrustRequestPromise;
}
//#endregion
//#region Workspace trust request
private resolveWorkspaceTrustRequest(trusted?: boolean): void {
if (this._workspaceTrustRequestResolver) {
this._workspaceTrustRequestResolver(trusted ?? this.trusted);
this._workspaceTrustRequestResolver = undefined;
this._workspaceTrustRequestPromise = undefined;
}
}
cancelWorkspaceTrustRequest(): void {
if (this._workspaceTrustRequestResolver) {
this._workspaceTrustRequestResolver(undefined);
this._workspaceTrustRequestResolver = undefined;
this._workspaceTrustRequestPromise = undefined;
}
}
async completeWorkspaceTrustRequest(trusted?: boolean): Promise<void> {
if (trusted === undefined || trusted === this.trusted) {
this.resolveWorkspaceTrustRequest(trusted);
return;
}
// Update storage, transition workspace, and resolve the promise
await this.workspaceTrustManagementService.setWorkspaceTrust(trusted);
this.resolveWorkspaceTrustRequest(trusted);
}
async requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise<boolean | undefined> {
@ -730,19 +734,21 @@ export class WorkspaceTrustRequestService extends Disposable implements IWorkspa
}
// Modal request
if (!this._modalTrustRequestPromise) {
if (!this._workspaceTrustRequestPromise) {
// Create promise
this._modalTrustRequestPromise = new Promise(resolve => {
this._modalTrustRequestResolver = resolve;
this._workspaceTrustRequestPromise = new Promise(resolve => {
this._workspaceTrustRequestResolver = resolve;
});
} else {
// Return existing promise
return this._modalTrustRequestPromise;
return this._workspaceTrustRequestPromise;
}
this._onDidInitiateWorkspaceTrustRequest.fire(options);
return this._modalTrustRequestPromise;
return this._workspaceTrustRequestPromise;
}
//#endregion
}
class WorkspaceTrustTransitionManager extends Disposable {

View file

@ -98,27 +98,31 @@ export class TestWorkspaceTrustManagementService implements IWorkspaceTrustManag
export class TestWorkspaceTrustRequestService implements IWorkspaceTrustRequestService {
_serviceBrand: any;
private readonly _onDidInitiateOpenFilesTrustRequest = new Emitter<void>();
readonly onDidInitiateOpenFilesTrustRequest = this._onDidInitiateOpenFilesTrustRequest.event;
private readonly _onDidInitiateWorkspaceTrustRequest = new Emitter<WorkspaceTrustRequestOptions>();
readonly onDidInitiateWorkspaceTrustRequest = this._onDidInitiateWorkspaceTrustRequest.event;
private readonly _onDidCompleteWorkspaceTrustRequest = new Emitter<boolean>();
readonly onDidCompleteWorkspaceTrustRequest = this._onDidCompleteWorkspaceTrustRequest.event;
constructor(private readonly _trusted: boolean) { }
requestOpenUrisHandler = async (uris: URI[]) => {
return WorkspaceTrustUriResponse.Open;
};
requestOpenUris(uris: URI[]): Promise<WorkspaceTrustUriResponse> {
requestOpenFilesTrust(uris: URI[]): Promise<WorkspaceTrustUriResponse> {
return this.requestOpenUrisHandler(uris);
}
cancelRequest(): void {
async completeOpenFilesTrustRequest(result: WorkspaceTrustUriResponse, saveResponse: boolean): Promise<void> {
throw new Error('Method not implemented.');
}
async completeRequest(trusted?: boolean): Promise<void> {
cancelWorkspaceTrustRequest(): void {
throw new Error('Method not implemented.');
}
async completeWorkspaceTrustRequest(trusted?: boolean): Promise<void> {
throw new Error('Method not implemented.');
}