blob: d8f54ac6d2640987152721040326a43113f71a2a [file] [log] [blame]
/* eslint-disable @typescript-eslint/no-explicit-any */
/*---------------------------------------------------------
* Copyright 2021 The Go Authors. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/
import vscode = require('vscode');
import { getFromWorkspaceState, updateWorkspaceState } from './stateUtils';
const WORKSPACE_IS_TRUSTED_KEY = 'WORKSPACE_IS_TRUSTED_KEY';
const SECURITY_SENSITIVE_CONFIG: string[] = [
'alternateTools',
'gopath',
'goroot',
'inferGopath',
'toolsGopath',
'toolsEnvVars'
];
// Set true only if the vscode is the recent version that has the workspace trust API AND
// if the security.workspace.trust is enabled. Change of this configuration requires restart
// of VSCode, so we don't need to set up the configuration change listener.
// TODO(hyangah): remove this and Configuration & WrappedConfiguration when we update
// our extension to require 2021 June VSCode engine.
const isVscodeWorkspaceTrustAPIAvailable =
'boolean' === typeof (vscode.workspace as any).isTrusted &&
vscode.workspace.getConfiguration('security.workspace.trust')?.get('enabled') === true;
// Initialize the singleton defaultConfig and register related commands.
// Prompt if workspace configuration was found but had to be ignored until
// the user has to explicitly opt in to trust the workspace.
export async function initConfig(ctx: vscode.ExtensionContext) {
ctx.subscriptions.push(vscode.commands.registerCommand('go.workspace.isTrusted.toggle', toggleWorkspaceIsTrusted));
if (isVscodeWorkspaceTrustAPIAvailable) {
return; // let vscode handle configuration management.
}
const isTrusted = getFromWorkspaceState(WORKSPACE_IS_TRUSTED_KEY, false);
if (isTrusted !== defaultConfig.workspaceIsTrusted()) {
defaultConfig.toggleWorkspaceIsTrusted();
}
if (isTrusted) {
return;
}
const ignored = ignoredWorkspaceConfig(vscode.workspace.getConfiguration('go'), SECURITY_SENSITIVE_CONFIG);
if (ignored.length === 0) {
return;
}
const ignoredSettings = ignored.map((x) => `"go.${x}"`).join(',');
const val = await vscode.window.showWarningMessage(
`Some workspace/folder-level settings (${ignoredSettings}) from the untrusted workspace are disabled ` +
'by default. If this workspace is trusted, explicitly enable the workspace/folder-level settings ' +
'by running the "Go: Toggle Workspace Trust Flag" command.',
'OK',
'Trust This Workspace',
'More Info'
);
switch (val) {
case 'Trust This Workspace':
await toggleWorkspaceIsTrusted();
break;
case 'More Info':
vscode.env.openExternal(
vscode.Uri.parse('https://github.com/golang/vscode-go/blob/master/docs/settings.md#security')
);
break;
default:
break;
}
}
function ignoredWorkspaceConfig(cfg: vscode.WorkspaceConfiguration, keys: string[]) {
return keys.filter((key) => {
const inspect = cfg.inspect(key);
return inspect.workspaceValue !== undefined || inspect.workspaceFolderValue !== undefined;
});
}
async function toggleWorkspaceIsTrusted() {
if (isVscodeWorkspaceTrustAPIAvailable) {
vscode.commands.executeCommand('workbench.action.manageTrust');
return;
}
const v = defaultConfig.toggleWorkspaceIsTrusted();
await updateWorkspaceState(WORKSPACE_IS_TRUSTED_KEY, v);
}
// Go extension configuration for a workspace.
export class Configuration {
constructor(private _workspaceIsTrusted = false, private getConfiguration = vscode.workspace.getConfiguration) {}
public toggleWorkspaceIsTrusted() {
this._workspaceIsTrusted = !this._workspaceIsTrusted;
return this._workspaceIsTrusted;
}
// returns a Proxied vscode.WorkspaceConfiguration, which prevents
// from using the workspace configuration if the workspace is untrusted.
public get(section: string, uri?: vscode.Uri): vscode.WorkspaceConfiguration {
const cfg = this.getConfiguration(section, uri);
if (section !== 'go' || this._workspaceIsTrusted) {
return cfg;
}
return new WrappedConfiguration(cfg);
}
public workspaceIsTrusted(): boolean {
return this._workspaceIsTrusted;
}
}
class vscodeConfiguration {
public toggleWorkspaceIsTrusted() {
/* no-op */
}
public get(section: string, uri?: vscode.Uri): vscode.WorkspaceConfiguration {
return vscode.workspace.getConfiguration(section, uri);
}
public workspaceIsTrusted(): boolean {
return !!(vscode.workspace as any).isTrusted;
}
}
const defaultConfig = isVscodeWorkspaceTrustAPIAvailable ? new vscodeConfiguration() : new Configuration();
// Returns the workspace Configuration used by the extension.
export function DefaultConfig() {
return defaultConfig;
}
// wrappedConfiguration wraps vscode.WorkspaceConfiguration.
class WrappedConfiguration implements vscode.WorkspaceConfiguration {
constructor(private readonly _wrapped: vscode.WorkspaceConfiguration) {
// set getters for direct setting access (e.g. cfg.gopath), but don't overwrite _wrapped.
const desc = Object.getOwnPropertyDescriptors(_wrapped);
for (const prop in desc) {
// TODO(hyangah): find a better way to exclude WrappedConfiguration's members.
// These methods are defined by WrappedConfiguration.
if (typeof prop === 'string' && !['get', 'has', 'inspect', 'update', '_wrapped'].includes(prop)) {
const d = desc[prop];
if (SECURITY_SENSITIVE_CONFIG.includes(prop)) {
const inspect = this._wrapped.inspect(prop);
d.value = inspect.globalValue ?? inspect.defaultValue;
}
Object.defineProperty(this, prop, desc[prop]);
}
}
}
public get(section: any, defaultValue?: any) {
if (SECURITY_SENSITIVE_CONFIG.includes(section)) {
const inspect = this._wrapped.inspect(section);
return inspect.globalValue ?? defaultValue ?? inspect.defaultValue;
}
return this._wrapped.get(section, defaultValue);
}
public has(section: string) {
return this._wrapped.has(section);
}
public inspect<T>(section: string) {
return this._wrapped.inspect<T>(section);
}
public update(
section: string,
value: any,
configurationTarget?: boolean | vscode.ConfigurationTarget,
overrideInLanguage?: boolean
): Thenable<void> {
return this._wrapped.update(section, value, configurationTarget, overrideInLanguage);
}
}
// getGoConfig is declared as an exported const rather than a function, so it can be stubbbed in testing.
export const getGoConfig = (uri?: vscode.Uri) => {
return getConfig('go', uri);
};
// getGoplsConfig returns the user's gopls configuration.
export function getGoplsConfig(uri?: vscode.Uri) {
return getConfig('gopls', uri);
}
function getConfig(section: string, uri?: vscode.Uri) {
if (!uri) {
if (vscode.window.activeTextEditor) {
uri = vscode.window.activeTextEditor.document.uri;
} else {
uri = null;
}
}
return defaultConfig.get(section, uri);
}
// True if the extension is running in known cloud-based IDEs.
export const IsInCloudIDE = process.env.CLOUD_SHELL === 'true' || process.env.CODESPACES === 'true';