Skip to content
Permalink
Browse files Browse the repository at this point in the history
Patch to address workspace security issue.
  • Loading branch information
lextm committed Apr 5, 2021
1 parent dc1b21f commit 1dd3e87
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 52 deletions.
32 changes: 23 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Expand Up @@ -240,6 +240,11 @@
"command": "restructuredtext.preview.toggleLock",
"title": "%restructuredtext.preview.toggleLock.title%",
"category": "reStructuredText"
},
{
"command": "restructuredtext.workspace.isTrusted.toggle",
"title": "reStructuredText: Toggle Workspace Trust Flag",
"description": "Toggle the workspace trust flag. Workspace settings that determine tool locations are disabled by default in untrusted workspaces."
}
],
"menus": {
Expand Down
151 changes: 151 additions & 0 deletions src/config.ts
@@ -0,0 +1,151 @@
/* 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[] = [
'sphinxBuildPath',
'linter.executablePath'
];

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('restructuredtext.workspace.isTrusted.toggle', toggleWorkspaceIsTrusted));

if (isTrusted) {
return;
}
const ignored = ignoredWorkspaceConfig(vscode.workspace.getConfiguration('restructuredtext'), SECURITY_SENSITIVE_CONFIG);
if (ignored.length === 0) {
return;
}
const ignoredSettings = ignored.map((x) => `"restructuredtext.${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 "reStructuredText: 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://docs.restructuredtext.net/articles/configuration.html#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);
}

// reStructuredText 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 !== 'restructuredtext' || 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.
// tslint:disable-next-line: max-classes-per-file
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);
}
}

export 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';
84 changes: 46 additions & 38 deletions src/extension.ts
Expand Up @@ -9,56 +9,64 @@ import * as commands from './commands/index';
import { RSTContentProvider } from './features/previewContentProvider';
import { RSTPreviewManager } from './features/previewManager';
import { Logger } from './logger';
import { ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './security';
import { Python } from './python';
import { RSTEngine } from './rstEngine';
import { ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './security';

import * as listEditing from './features/listEditing';
import { rstDocumentSymbolProvider } from './features/rstDocumentSymbolProvider';
import RstLintingProvider from './features/rstLinter';
import { underline } from './features/underline';
import { Configuration } from './features/utils/configuration';
import RstTransformerStatus from './features/utils/statusBar';
import * as RstLanguageServer from './rstLsp/extension';
import { rstDocumentSymbolProvider } from './features/rstDocumentSymbolProvider';
import { setGlobalState, setWorkspaceState } from './stateUtils';
import { initConfig } from './config';

let extensionPath = "";
let extensionPath = '';

export function getExtensionPath(): string {
return extensionPath;
return extensionPath;
}

export async function activate(context: vscode.ExtensionContext): Promise<{ initializationFinished: Promise<void> }> {
extensionPath = context.extensionPath;

const logger = new Logger();
logger.log('Please visit https://docs.restructuredtext.net to learn how to configure the extension.');
setGlobalState(context.globalState);
setWorkspaceState(context.workspaceState);

await initConfig(context);

extensionPath = context.extensionPath;

const conflicting = Configuration.getConflictingExtensions();
for (const element of conflicting) {
const found = vscode.extensions.getExtension(element);
if (found) {
const message = `Found conflicting extension ${element}. Please uninstall it.`;
const logger = new Logger();
logger.log('Please visit https://docs.restructuredtext.net to learn how to configure the extension.');

const conflicting = Configuration.getConflictingExtensions();
for (const element of conflicting) {
const found = vscode.extensions.getExtension(element);
if (found) {
const message = `Found conflicting extension ${element}. Please uninstall it.`;
logger.log(message);
vscode.window.showErrorMessage(message);
}
}
vscode.window.showErrorMessage(message);
}
}

await logPlatform(logger);
const disableLsp = Configuration.getLanguageServerDisabled();
const disableLsp = Configuration.getLanguageServerDisabled();

const python: Python = new Python(logger);

// activate language services
const rstLspPromise = RstLanguageServer.activate(context, logger, disableLsp, python);
// activate language services
const rstLspPromise = RstLanguageServer.activate(context, logger, disableLsp, python);

// Section creation support.
context.subscriptions.push(
vscode.commands.registerTextEditorCommand('restructuredtext.features.underline.underline', underline),
vscode.commands.registerTextEditorCommand('restructuredtext.features.underline.underlineReverse',
(textEditor, edit) => underline(textEditor, edit, true)),
);
context.subscriptions.push(
vscode.commands.registerTextEditorCommand('restructuredtext.features.underline.underline', underline),
vscode.commands.registerTextEditorCommand('restructuredtext.features.underline.underlineReverse',
(textEditor, edit) => underline(textEditor, edit, true)),
);

// Linter support
// Linter support
if (!Configuration.getLinterDisabled()) {
const linter = new RstLintingProvider(logger, python);
linter.activate(context.subscriptions);
Expand All @@ -76,7 +84,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init

vscode.window.onDidChangeActiveTextEditor(status.update, status, context.subscriptions);
status.update();

const cspArbiter = new ExtensionContentSecurityPolicyArbiter(context.globalState, context.workspaceState);

const engine: RSTEngine = new RSTEngine(python, logger, status);
Expand Down Expand Up @@ -104,7 +112,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init
previewManager.updateConfiguration();
}));
}

// DocumentSymbolProvider Demo, for Outline View Test
let disposableRstDSP = vscode.languages.registerDocumentSymbolProvider(
{ scheme: 'file', language: 'restructuredtext' }, new rstDocumentSymbolProvider()
Expand All @@ -113,23 +121,23 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init

listEditing.activate(context);

return {
initializationFinished: Promise.all([rstLspPromise])
.then((promiseResult) => {
// This promise resolver simply swallows the result of Promise.all.
// When we decide we want to expose this level of detail
// to other extensions then we will design that return type and implement it here.
}),
};
return {
initializationFinished: Promise.all([rstLspPromise])
.then((promiseResult) => {
// This promise resolver simply swallows the result of Promise.all.
// When we decide we want to expose this level of detail
// to other extensions then we will design that return type and implement it here.
}),
};
}

async function logPlatform(logger: Logger): Promise<void> {
const os = require('os');
const os = require('os');
let platform = os.platform();
logger.log(`OS is ${platform}`);
if (platform === 'darwin' || platform === 'win32') {
return;
}
if (platform === 'darwin' || platform === 'win32') {
return;
}

const osInfo = require('linux-os-info');
const result = await osInfo();
Expand Down
12 changes: 7 additions & 5 deletions src/features/utils/configuration.ts
@@ -1,10 +1,11 @@
'use strict';

import {
Uri, workspace, WorkspaceFolder, extensions, WorkspaceConfiguration
} from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import {
extensions, Uri, workspace, WorkspaceConfiguration, WorkspaceFolder
} from 'vscode';
import { getConfig } from '../../config';
import { Constants } from './constants';

export class Configuration {
Expand Down Expand Up @@ -154,13 +155,14 @@ export class Configuration {
private static loadAnySetting<T>(
configSection: string, defaultValue: T, resource: Uri, header: string = 'restructuredtext',
): T {
return workspace.getConfiguration(header, resource).get(configSection, defaultValue);
// return workspace.getConfiguration(header, resource).get(configSection, defaultValue);
return getConfig(header, resource).get(configSection, defaultValue);
}

private static async saveAnySetting<T>(
configSection: string, value: T, resource: Uri, header: string = 'restructuredtext',
): Promise<T> {
await workspace.getConfiguration(header, resource).update(configSection, value);
await getConfig(header, resource).update(configSection, value);
return value;
}

Expand Down

0 comments on commit 1dd3e87

Please sign in to comment.