Workspace trust changes (#119017)
* Add dialog button customisation and reject promise if cancelled * Use different promises to modal/soft requests
This commit is contained in:
parent
e787d6e384
commit
149a8b71c5
|
@ -9,7 +9,9 @@
|
|||
"license": "MIT",
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
"enableProposedApi": true,
|
||||
"requiresWorkspaceTrust": "onDemand",
|
||||
"workspaceTrust": {
|
||||
"required": "onDemand"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.30.0"
|
||||
},
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
"publisher": "vscode",
|
||||
"license": "MIT",
|
||||
"enableProposedApi": true,
|
||||
"requiresWorkspaceTrust": "onDemand",
|
||||
"workspaceTrust": {
|
||||
"required": "onDemand"
|
||||
},
|
||||
"private": true,
|
||||
"activationEvents": [],
|
||||
"main": "./out/extension",
|
||||
|
|
|
@ -156,8 +156,8 @@ export interface IExtensionContributions {
|
|||
}
|
||||
|
||||
export type ExtensionKind = 'ui' | 'workspace' | 'web';
|
||||
|
||||
export type ExtensionWorkspaceTrustRequirement = false | 'onStart' | 'onDemand';
|
||||
export type ExtensionWorkspaceTrust = { required: ExtensionWorkspaceTrustRequirement, description?: string };
|
||||
|
||||
export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier {
|
||||
return thing
|
||||
|
@ -214,7 +214,7 @@ export interface IExtensionManifest {
|
|||
readonly enableProposedApi?: boolean;
|
||||
readonly api?: string;
|
||||
readonly scripts?: { [key: string]: string; };
|
||||
readonly requiresWorkspaceTrust?: ExtensionWorkspaceTrustRequirement;
|
||||
readonly workspaceTrust?: ExtensionWorkspaceTrust;
|
||||
}
|
||||
|
||||
export const enum ExtensionType {
|
||||
|
|
|
@ -44,7 +44,14 @@ export interface IWorkspaceTrustModel {
|
|||
getTrustStateInfo(): IWorkspaceTrustStateInfo;
|
||||
}
|
||||
|
||||
export interface WorkspaceTrustRequestButton {
|
||||
label: string;
|
||||
type: 'ContinueWithTrust' | 'ContinueWithoutTrust' | 'Manage' | 'Cancel'
|
||||
}
|
||||
|
||||
export interface WorkspaceTrustRequest {
|
||||
buttons?: WorkspaceTrustRequestButton[];
|
||||
message?: string;
|
||||
modal: boolean;
|
||||
}
|
||||
|
||||
|
@ -53,9 +60,11 @@ export interface IWorkspaceTrustRequestModel {
|
|||
|
||||
readonly onDidInitiateRequest: Event<void>;
|
||||
readonly onDidCompleteRequest: Event<WorkspaceTrustState | undefined>;
|
||||
readonly onDidCancelRequest: Event<void>;
|
||||
|
||||
initiateRequest(request?: WorkspaceTrustRequest): void;
|
||||
completeRequest(trustState?: WorkspaceTrustState): void;
|
||||
cancelRequest(): void;
|
||||
}
|
||||
|
||||
export interface WorkspaceTrustStateChangeEvent {
|
||||
|
|
|
@ -90,38 +90,49 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben
|
|||
this.telemetryService.publicLog2<WorkspaceTrustRequestedEvent, WorkspaceTrustRequestedEventClassification>('workspaceTrustRequested', {
|
||||
modal: this.requestModel.trustRequest.modal,
|
||||
workspaceId: this.workspaceContextService.getWorkspace().id,
|
||||
extensions: (await this.extensionService.getExtensions()).filter(ext => !!ext.requiresWorkspaceTrust).map(ext => ext.identifier.value)
|
||||
extensions: (await this.extensionService.getExtensions()).filter(ext => !!ext.workspaceTrust).map(ext => ext.identifier.value)
|
||||
});
|
||||
|
||||
if (this.requestModel.trustRequest.modal) {
|
||||
// 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 = this.requestModel.trustRequest.message ?? defaultMessage;
|
||||
|
||||
// Buttons
|
||||
const buttons = this.requestModel.trustRequest.buttons ?? [
|
||||
{ label: localize('grantWorkspaceTrustButton', "Continue"), type: 'ContinueWithTrust' },
|
||||
{ label: localize('manageWorkspaceTrustButton', "Learn More"), type: 'Manage' }
|
||||
];
|
||||
// Add Cancel button if not provided
|
||||
if (!buttons.some(b => b.type === 'Cancel')) {
|
||||
buttons.push({ label: localize('cancelWorkspaceTrustButton', "Cancel"), type: 'Cancel' });
|
||||
}
|
||||
|
||||
// Dialog
|
||||
const result = await this.dialogService.show(
|
||||
Severity.Warning,
|
||||
localize('immediateTrustRequestTitle', "Do you trust the files in this folder?"),
|
||||
[
|
||||
localize('grantWorkspaceTrustButton', "Trust"),
|
||||
localize('denyWorkspaceTrustButton', "Don't Trust"),
|
||||
localize('manageWorkspaceTrustButton', "Manage"),
|
||||
localize('cancelWorkspaceTrustButton', "Cancel"),
|
||||
],
|
||||
buttons.map(b => b.label),
|
||||
{
|
||||
cancelId: 3,
|
||||
detail: localize('immediateTrustRequestDetail', "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.\n\nYou should only trust this workspace if you trust its source. Otherwise, features will be enabled that may compromise your device or personal information."),
|
||||
cancelId: buttons.findIndex(b => b.type === 'Cancel'),
|
||||
detail: localize('immediateTrustRequestDetail', "{0}\n\nYou should only trust this workspace if you trust its source. Otherwise, features will be enabled that may compromise your device or personal information.", message),
|
||||
}
|
||||
);
|
||||
|
||||
switch (result.choice) {
|
||||
case 0: // Trust
|
||||
// Dialog result
|
||||
switch (buttons[result.choice].type) {
|
||||
case 'ContinueWithTrust':
|
||||
this.requestModel.completeRequest(WorkspaceTrustState.Trusted);
|
||||
break;
|
||||
case 1: // Don't Trust
|
||||
this.requestModel.completeRequest(WorkspaceTrustState.Untrusted);
|
||||
break;
|
||||
case 2: // Manage
|
||||
case 'ContinueWithoutTrust':
|
||||
this.requestModel.completeRequest(undefined);
|
||||
break;
|
||||
case 'Manage':
|
||||
this.requestModel.cancelRequest();
|
||||
await this.commandService.executeCommand('workbench.trust.manage');
|
||||
break;
|
||||
default: // Cancel
|
||||
this.requestModel.completeRequest(undefined);
|
||||
case 'Cancel':
|
||||
this.requestModel.cancelRequest();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -248,7 +248,7 @@ export class WorkspaceTrustEditor extends EditorPane {
|
|||
}
|
||||
|
||||
private async getExtensionsByTrustRequirement(extensions: IExtensionStatus[], trustRequirement: ExtensionWorkspaceTrustRequirement): Promise<IExtension[]> {
|
||||
const filtered = extensions.filter(ext => ext.local.manifest.requiresWorkspaceTrust === trustRequirement);
|
||||
const filtered = extensions.filter(ext => ext.local.manifest.workspaceTrust?.required === trustRequirement);
|
||||
const ids = filtered.map(ext => ext.identifier.id);
|
||||
|
||||
return getExtensions(ids, this.extensionWorkbenchService);
|
||||
|
|
|
@ -281,7 +281,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
|||
private _isDisabledByTrustRequirement(extension: IExtension): boolean {
|
||||
const workspaceTrustState = this.workspaceTrustService.getWorkspaceTrustState();
|
||||
|
||||
if (extension.manifest.requiresWorkspaceTrust === 'onStart') {
|
||||
if (extension.manifest.workspaceTrust?.required === 'onStart') {
|
||||
if (workspaceTrustState !== WorkspaceTrustState.Trusted) {
|
||||
this._addToWorkspaceDisabledExtensionsByTrustRequirement(extension);
|
||||
}
|
||||
|
|
|
@ -361,7 +361,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
|
|||
}
|
||||
|
||||
protected async checkForWorkspaceTrust(manifest: IExtensionManifest): Promise<void> {
|
||||
if (manifest.requiresWorkspaceTrust === 'onStart') {
|
||||
if (manifest.workspaceTrust?.required === 'onStart') {
|
||||
const trustState = await this.workspaceTrustService.requireWorkspaceTrust();
|
||||
return trustState === WorkspaceTrustState.Trusted ? Promise.resolve() : Promise.reject(canceled());
|
||||
}
|
||||
|
|
|
@ -270,13 +270,13 @@ export function throwProposedApiError(extension: IExtensionDescription): never {
|
|||
}
|
||||
|
||||
export function checkRequiresWorkspaceTrust(extension: IExtensionDescription): void {
|
||||
if (!extension.requiresWorkspaceTrust) {
|
||||
if (!extension.workspaceTrust?.required) {
|
||||
throwRequiresWorkspaceTrustError(extension);
|
||||
}
|
||||
}
|
||||
|
||||
export function throwRequiresWorkspaceTrustError(extension: IExtensionDescription): void {
|
||||
throw new Error(`[${extension.identifier.value}]: This API is only available when the "requiresWorkspaceTrust" is set to "onStart" or "onDemand" in the extension's package.json.`);
|
||||
throw new Error(`[${extension.identifier.value}]: This API is only available when the "workspaceTrust.require" is set to "onStart" or "onDemand" in the extension's package.json.`);
|
||||
}
|
||||
|
||||
export function toExtension(extensionDescription: IExtensionDescription): IExtension {
|
||||
|
|
|
@ -16,6 +16,7 @@ import { isEqual, isEqualOrParent } from 'vs/base/common/extpath';
|
|||
import { EditorModel } from 'vs/workbench/common/editor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { dirname, resolve } from 'vs/base/common/path';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
|
||||
export const WORKSPACE_TRUST_ENABLED = 'workspace.trustEnabled';
|
||||
export const WORKSPACE_TRUST_STORAGE_KEY = 'content.trust.model.key';
|
||||
|
@ -191,6 +192,9 @@ export class WorkspaceTrustRequestModel extends Disposable implements IWorkspace
|
|||
private readonly _onDidCompleteRequest = this._register(new Emitter<WorkspaceTrustState | undefined>());
|
||||
readonly onDidCompleteRequest = this._onDidCompleteRequest.event;
|
||||
|
||||
private readonly _onDidCancelRequest = this._register(new Emitter<void>());
|
||||
readonly onDidCancelRequest = this._onDidCancelRequest.event;
|
||||
|
||||
initiateRequest(request: WorkspaceTrustRequest): void {
|
||||
if (this.trustRequest && (!request.modal || this.trustRequest.modal)) {
|
||||
return;
|
||||
|
@ -204,6 +208,11 @@ export class WorkspaceTrustRequestModel extends Disposable implements IWorkspace
|
|||
this.trustRequest = undefined;
|
||||
this._onDidCompleteRequest.fire(trustState);
|
||||
}
|
||||
|
||||
cancelRequest(): void {
|
||||
this.trustRequest = undefined;
|
||||
this._onDidCancelRequest.fire();
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkspaceTrustService extends Disposable implements IWorkspaceTrustService {
|
||||
|
@ -217,8 +226,11 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust
|
|||
readonly onDidChangeTrustState = this._onDidChangeTrustState.event;
|
||||
|
||||
private _currentTrustState: WorkspaceTrustState = WorkspaceTrustState.Unknown;
|
||||
private _inFlightResolver?: (trustState: WorkspaceTrustState) => void;
|
||||
private _trustRequestPromise?: Promise<WorkspaceTrustState>;
|
||||
private _inFlightResolver?: (trustState: WorkspaceTrustState) => void;
|
||||
private _modalTrustRequestPromise?: Promise<WorkspaceTrustState>;
|
||||
private _modalTrustRequestResolver?: (trustState: WorkspaceTrustState) => void;
|
||||
private _modalTrustRequestRejecter?: (error: Error) => void;
|
||||
private _workspace: IWorkspace;
|
||||
|
||||
private readonly _ctxWorkspaceTrustState: IContextKey<WorkspaceTrustState>;
|
||||
|
@ -243,6 +255,7 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust
|
|||
|
||||
this._register(this.dataModel.onDidChangeTrustState(() => this.currentTrustState = this.calculateWorkspaceTrustState()));
|
||||
this._register(this.requestModel.onDidCompleteRequest((trustState) => this.onTrustRequestCompleted(trustState)));
|
||||
this._register(this.requestModel.onDidCancelRequest(() => this.onTrustRequestCancelled()));
|
||||
|
||||
this._ctxWorkspaceTrustState = WorkspaceTrustContext.TrustState.bindTo(contextKeyService);
|
||||
this._ctxWorkspaceTrustPendingRequest = WorkspaceTrustContext.PendingRequest.bindTo(contextKeyService);
|
||||
|
@ -358,6 +371,9 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust
|
|||
}
|
||||
|
||||
private onTrustRequestCompleted(trustState?: WorkspaceTrustState): void {
|
||||
if (this._modalTrustRequestResolver) {
|
||||
this._modalTrustRequestResolver(trustState === undefined ? this.currentTrustState : trustState);
|
||||
}
|
||||
if (this._inFlightResolver) {
|
||||
this._inFlightResolver(trustState === undefined ? this.currentTrustState : trustState);
|
||||
}
|
||||
|
@ -365,6 +381,10 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust
|
|||
this._inFlightResolver = undefined;
|
||||
this._trustRequestPromise = undefined;
|
||||
|
||||
this._modalTrustRequestResolver = undefined;
|
||||
this._modalTrustRequestRejecter = undefined;
|
||||
this._modalTrustRequestPromise = undefined;
|
||||
|
||||
if (trustState === undefined) {
|
||||
return;
|
||||
}
|
||||
|
@ -377,6 +397,16 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust
|
|||
this._ctxWorkspaceTrustState.set(trustState);
|
||||
}
|
||||
|
||||
private onTrustRequestCancelled(): void {
|
||||
if (this._modalTrustRequestRejecter) {
|
||||
this._modalTrustRequestRejecter(canceled());
|
||||
}
|
||||
|
||||
this._modalTrustRequestResolver = undefined;
|
||||
this._modalTrustRequestRejecter = undefined;
|
||||
this._modalTrustRequestPromise = undefined;
|
||||
}
|
||||
|
||||
getWorkspaceTrustState(): WorkspaceTrustState {
|
||||
return this.currentTrustState;
|
||||
}
|
||||
|
@ -386,31 +416,42 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust
|
|||
}
|
||||
|
||||
async requireWorkspaceTrust(request: WorkspaceTrustRequest = { modal: true }): Promise<WorkspaceTrustState> {
|
||||
// Trusted workspace
|
||||
if (this.currentTrustState === WorkspaceTrustState.Trusted) {
|
||||
return this.currentTrustState;
|
||||
}
|
||||
// Untrusted workspace - soft request
|
||||
if (this.currentTrustState === WorkspaceTrustState.Untrusted && !request.modal) {
|
||||
return this.currentTrustState;
|
||||
}
|
||||
|
||||
if (this._trustRequestPromise) {
|
||||
if (request.modal &&
|
||||
this.requestModel.trustRequest &&
|
||||
!this.requestModel.trustRequest.modal) {
|
||||
this.requestModel.initiateRequest(request);
|
||||
if (request.modal) {
|
||||
// Modal request
|
||||
if (!this._modalTrustRequestPromise) {
|
||||
// Create promise
|
||||
this._modalTrustRequestPromise = new Promise((resolve, reject) => {
|
||||
this._modalTrustRequestResolver = resolve;
|
||||
this._modalTrustRequestRejecter = reject;
|
||||
});
|
||||
} else {
|
||||
// Return existing promises
|
||||
return this._modalTrustRequestPromise;
|
||||
}
|
||||
} else {
|
||||
// Soft request
|
||||
if (!this._trustRequestPromise) {
|
||||
this._trustRequestPromise = new Promise(resolve => {
|
||||
this._inFlightResolver = resolve;
|
||||
});
|
||||
} else {
|
||||
return this._trustRequestPromise;
|
||||
}
|
||||
|
||||
return this._trustRequestPromise;
|
||||
}
|
||||
|
||||
this._trustRequestPromise = new Promise(resolve => {
|
||||
this._inFlightResolver = resolve;
|
||||
});
|
||||
|
||||
this.requestModel.initiateRequest(request);
|
||||
this._ctxWorkspaceTrustPendingRequest.set(true);
|
||||
|
||||
return this._trustRequestPromise;
|
||||
return request.modal ? this._modalTrustRequestPromise! : this._trustRequestPromise!;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue