src/config.ts: initialize the Configuration singleton during activation

Prompt the user if the workspace isn't trusted but workspace config is found.

And register 'go.workspace.isTrusted.toggle' (Go: Toggle Workspace Trust Flag)
command.

Change-Id: Iaaa2990a98b60bbbf1d59d5c18e5043e1386d44f
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/283254
Trust: Hyang-Ah Hana Kim <hyangah@gmail.com>
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
Reviewed-by: Suzy Mueller <suzmue@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/docs/commands.md b/docs/commands.md
index 6914126..03ae8a4 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -206,3 +206,7 @@
 ### `Go: Reset Survey Configuration`
 
 Reset the current Go survey configuration history
+
+### `Go: Toggle Workspace Trust Flag`
+
+Toggle the workspace trust flag. Workspace settings that determine tool locations are disabled by default in untrusted workspaces.
diff --git a/docs/settings.md b/docs/settings.md
index b245d02..7a48be4 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -17,6 +17,10 @@
 3. Click on the `Feature Contributions` tab.
 4. Scroll through the list under `Settings`.
 
+## Security
+
+This extension runs a few [third-party command-line tools](tools.md) found from the locations determined by the `PATH` or `Path` environment variable, and the settings such as `"go.alternateTools"` or `"go.toolsGopath"`. Configuring them in workspace settings allows users to conveniently select a different set of tools based on project's need, but also allows attackers to run arbitrary binaries on your machine if they successfuly convince you to open a random repository. In order to reduce the security risk, the extension reads those settings from user settings by default. If the repository can be trusted and workspace settings must be used, you can mark the workspace as a trusted workspace using the `"Go: Toggle Workspace Trust Flag"` command.
+
 ## Detailed list
 
 <!-- Everything below this line is generated. DO NOT EDIT. -->
diff --git a/package.json b/package.json
index f2a9466..2753baf 100644
--- a/package.json
+++ b/package.json
@@ -400,6 +400,11 @@
         "command": "go.survey.resetConfig",
         "title": "Go: Reset Survey Configuration",
         "description": "Reset the current Go survey configuration history"
+      },
+      {
+        "command": "go.workspace.isTrusted.toggle",
+        "title": "Go: Toggle Workspace Trust Flag",
+        "description": "Toggle the workspace trust flag. Workspace settings that determine tool locations are disabled by default in untrusted workspaces."
       }
     ],
     "breakpoints": [
diff --git a/src/config.ts b/src/config.ts
index b12ba51..cffad71 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -4,22 +4,76 @@
  *--------------------------------------------------------*/
 
 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'
 ];
 
+let defaultConfig: Configuration = null;
+
+// 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);
+	defaultConfig = new Configuration(isTrusted, vscode.workspace.getConfiguration);
+	ctx.subscriptions.push(
+		vscode.commands.registerCommand('go.workspace.isTrusted.toggle', 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 defaultConfig.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;
+	});
+}
+
 // Go extension configuration for a workspace.
 export class Configuration {
 	constructor(
-		private isTrustedWorkspace: boolean,
+		private workspaceIsTrusted: boolean,
 		private getConfiguration: typeof vscode.workspace.getConfiguration) { }
 
+	public async toggleWorkspaceIsTrusted() {
+		this.workspaceIsTrusted = !this.workspaceIsTrusted;
+		await updateWorkspaceState(WORKSPACE_IS_TRUSTED_KEY, this.workspaceIsTrusted);
+	}
+
 	// returns a Proxied vscode.WorkspaceConfiguration, which prevents
 	// from using the workspace configuration if the workspace is untrusted.
 	public get<T>(uri?: vscode.Uri): vscode.WorkspaceConfiguration {
 		const cfg = this.getConfiguration('go', uri);
-		if (this.isTrustedWorkspace) {
+		if (this.workspaceIsTrusted) {
 			return cfg;
 		}
 
diff --git a/src/goMain.ts b/src/goMain.ts
index 0824136..21ad7d2 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -9,6 +9,7 @@
 import * as path from 'path';
 import semver = require('semver');
 import vscode = require('vscode');
+import { initConfig } from './config';
 import { extensionId } from './const';
 import { browsePackages } from './goBrowsePackage';
 import { buildCode } from './goBuild';
@@ -83,17 +84,20 @@
 // the configuration of the server.
 export let restartLanguageServer = () => { return; };
 
-export function activate(ctx: vscode.ExtensionContext) {
+export async function activate(ctx: vscode.ExtensionContext) {
 	if (process.env['VSCODE_GO_IN_TEST'] === '1') {  // Make sure this does not run when running in test.
 		return;
 	}
-	const cfg = getGoConfig();
-	setLogConfig(cfg['logging']);
 
 	setGlobalState(ctx.globalState);
 	setWorkspaceState(ctx.workspaceState);
 	setEnvironmentVariableCollection(ctx.environmentVariableCollection);
 
+	await initConfig(ctx);
+
+	const cfg = getGoConfig();
+	setLogConfig(cfg['logging']);
+
 	if (vscode.window.registerWebviewPanelSerializer) {
 		// Make sure we register a serializer in activation event
 		vscode.window.registerWebviewPanelSerializer(WelcomePanel.viewType, {