blob: 6e367b0b1f91bb8856cb854fc7a8a9b32ec9b8bd [file] [log] [blame]
/*---------------------------------------------------------
* 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[] = [
'goroot', 'gopath', 'toolsGopath', 'alternateTools', 'inferGopath'
];
// 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) {
const isTrusted = getFromWorkspaceState(WORKSPACE_IS_TRUSTED_KEY, false);
if (isTrusted !== defaultConfig.workspaceIsTrusted()) {
defaultConfig.toggleWorkspaceIsTrusted();
}
ctx.subscriptions.push(
vscode.commands.registerCommand('go.workspace.isTrusted.toggle', 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() {
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<T>(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;
}
}
const defaultConfig = 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);
}