提交 8c6dfd7e 编写于 作者: K Kai Maetzel 提交者: GitHub

Merge pull request #19107 from Microsoft/kieferrm/workspaceExecutables

Safe handling of workspace executables
......@@ -52,7 +52,8 @@
"php.validate.executablePath": {
"type": ["string", "null"],
"default": null,
"description": "%configuration.validate.executablePath%"
"description": "%configuration.validate.executablePath%",
"isExecutable": true
},
"php.validate.run": {
"type": "string",
......
......@@ -88,7 +88,8 @@
"null"
],
"default": null,
"description": "%typescript.tsdk.desc%"
"description": "%typescript.tsdk.desc%",
"isExecutable": true
},
"typescript.disableAutomaticTypeAcquisition": {
"type": "boolean",
......
......@@ -106,11 +106,13 @@ export interface IConfigModel<T> {
overrides: IOverrides<T>[];
keys: string[];
raw: any;
unfilteredRaw: any;
errors: any[];
merge(other: IConfigModel<T>, overwrite?: boolean): IConfigModel<T>;
config<V>(section: string): IConfigModel<V>;
configWithOverrides<V>(identifier: string, section?: string): IConfigModel<V>;
refilter(): void;
}
export interface IOverrides<T> {
......
......@@ -17,6 +17,13 @@ export const Extensions = {
Configuration: 'base.contributions.configuration'
};
// Locally extend IJSONSchema with the vscode-specific `isExecutable` property
declare module 'vs/base/common/jsonSchema' {
export interface IJSONSchema {
isExecutable?: boolean;
}
}
export interface IConfigurationRegistry {
/**
......@@ -254,6 +261,32 @@ function getDefaultValue(type: string | string[]): any {
const configurationRegistry = new ConfigurationRegistry();
Registry.add(Extensions.Configuration, configurationRegistry);
export interface ISecurityConfiguration {
security: {
workspacesTrustedToSpecifyExecutables: { [path: string]: boolean }
};
}
configurationRegistry.registerConfiguration({
'id': 'Security',
'order': 5,
'title': nls.localize('securityConfigurationTitle', "Security"),
'type': 'object',
'properties': {
'security.workspacesTrustedToSpecifyExecutables': {
'type': 'object',
'description': nls.localize('security.workspacesTrustedToSpecifyExecutables', "Specifes which workspaces are trusted to specify executables in their settings. This option can only configured in the user settings."),
'default': {},
defaultSnippets: [{ body: '${1:workspace_path} : ${2:true}' }],
'additionalProperties': {
'type': 'boolean',
'description': nls.localize('exclude.boolean', "Path to a workspaces. Set to true or false to trust or distrust a workspace."),
}
}
}
});
const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IConfigurationNode>('configuration', [], {
description: nls.localize('vscode.extension.contributes.configuration', 'Contributes configuration settings.'),
type: 'object',
......@@ -267,9 +300,19 @@ const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IConfigu
description: nls.localize('vscode.extension.contributes.configuration.properties', 'Description of the configuration properties.'),
type: 'object',
additionalProperties: {
$ref: 'http://json-schema.org/draft-04/schema#'
anyOf: [
{ $ref: 'http://json-schema.org/draft-04/schema#' },
{
type: 'object',
properties: {
isExecutable: {
type: 'boolean'
}
}
}
]
}
}
},
}
});
......
......@@ -92,6 +92,7 @@ export class ConfigModel<T> implements IConfigModel<T> {
protected _overrides: IOverrides<T>[] = [];
private _raw: any = {};
private _unfilteredRaw: any = {};
private _parseErrors: any[] = [];
constructor(content: string, private name: string = '') {
......@@ -116,6 +117,10 @@ export class ConfigModel<T> implements IConfigModel<T> {
return this._raw;
}
public get unfilteredRaw(): T {
return this._unfilteredRaw;
}
public get errors(): any[] {
return this._parseErrors;
}
......@@ -222,7 +227,10 @@ export class ConfigModel<T> implements IConfigModel<T> {
this._raw = <T>{};
this._parseErrors = [e];
}
this._unfilteredRaw = this._raw;
this._raw = this.filterRaw(this._unfilteredRaw);
this._contents = toValuesTree(this._raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`));
const configurationProperties = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
this._overrides = overrides.map<IOverrides<T>>(override => {
// Filter unknown and non-overridable properties
......@@ -238,6 +246,22 @@ export class ConfigModel<T> implements IConfigModel<T> {
};
});
}
/*
* If filterRaw is not a no-op, the returned object needs to be a copy.
* The input may not be modified in place. The default implementation
* is a no op.
*/
protected filterRaw(raw: any): any {
return raw;
}
public refilter(): void {
if (this._unfilteredRaw) {
this._raw = this.filterRaw(this._unfilteredRaw);
this._contents = toValuesTree(this._raw, message => console.error(`Conflict in settings file ${this.name}: ${message}`));
}
}
}
export class DefaultConfigModel<T> extends ConfigModel<T> {
......
......@@ -80,6 +80,8 @@ import 'vs/workbench/parts/terminal/electron-browser/terminalPanel'; // can be p
import 'vs/workbench/electron-browser/workbench';
import 'vs/workbench/parts/trust/electron-browser/trust.contribution';
import 'vs/workbench/parts/tasks/electron-browser/task.contribution';
import 'vs/workbench/parts/emmet/browser/emmet.browser.contribution';
......
......@@ -36,17 +36,20 @@ DEFAULT_TERMINAL_LINUX_READY.then(defaultTerminalLinux => {
'terminal.external.windowsExec': {
'type': 'string',
'description': nls.localize('terminal.external.windowsExec', "Customizes which terminal to run on Windows."),
'default': DEFAULT_TERMINAL_WINDOWS
'default': DEFAULT_TERMINAL_WINDOWS,
'isExecutable': true
},
'terminal.external.osxExec': {
'type': 'string',
'description': nls.localize('terminal.external.osxExec', "Customizes which terminal application to run on OS X."),
'default': DEFAULT_TERMINAL_OSX
'default': DEFAULT_TERMINAL_OSX,
'isExecutable': true
},
'terminal.external.linuxExec': {
'type': 'string',
'description': nls.localize('terminal.external.linuxExec', "Customizes which terminal to run on Linux."),
'default': defaultTerminalLinux
'default': defaultTerminalLinux,
'isExecutable': true
}
}
});
......
......@@ -196,7 +196,8 @@ export function registerContributions(): void {
'git.path': {
type: ['string', 'null'],
description: nls.localize('gitPath', "Path to the git executable"),
default: null
default: null,
isExecutable: true
},
'git.autorefresh': {
type: 'boolean',
......
......@@ -56,6 +56,8 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { VSash } from 'vs/base/browser/ui/sash/sash';
import { Widget } from 'vs/base/browser/ui/widget';
import { overrideIdentifierFromKey } from 'vs/platform/configuration/common/model';
import { IMarkerService, IMarkerData } from 'vs/platform/markers/common/markers';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
// Ignore following contributions
import { FoldingController } from 'vs/editor/contrib/folding/browser/folding';
......@@ -568,6 +570,7 @@ export class SettingsRenderer extends Disposable implements IPreferencesRenderer
private editSettingActionRenderer: EditSettingRenderer;
private highlightPreferencesRenderer: HighlightPreferencesRenderer;
private defaultSettingsModel: DefaultSettingsEditorModel;
private untrustedSettingRenderer: UnTrustedWorkspaceSettingsRenderer;
private modelChangeDelayer: Delayer<void> = new Delayer<void>(200);
private _onFocusPreference: Emitter<ISetting> = new Emitter<ISetting>();
......@@ -584,6 +587,9 @@ export class SettingsRenderer extends Disposable implements IPreferencesRenderer
@IInstantiationService protected instantiationService: IInstantiationService
) {
super();
if (this.preferencesService.workspaceSettingsResource.toString() === preferencesModel.uri.toString()) {
this.untrustedSettingRenderer = this._register(instantiationService.createInstance(UnTrustedWorkspaceSettingsRenderer, editor, preferencesModel));
}
this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor, this._onFocusPreference, this._onClearFocusPreference));
this.highlightPreferencesRenderer = this._register(instantiationService.createInstance(HighlightPreferencesRenderer, editor));
this.initializationPromise = this.initialize();
......@@ -596,6 +602,9 @@ export class SettingsRenderer extends Disposable implements IPreferencesRenderer
public render(): void {
this.initializationPromise.then(() => {
this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups);
if (this.untrustedSettingRenderer) {
this.untrustedSettingRenderer.render();
}
});
}
......@@ -1339,6 +1348,42 @@ class SettingHighlighter extends Disposable {
}
}
class UnTrustedWorkspaceSettingsRenderer extends Disposable {
constructor(private editor: editorCommon.ICommonCodeEditor, private workspaceSettingsEditorModel: SettingsEditorModel,
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
@IMarkerService private markerService: IMarkerService
) {
super();
}
public render(): void {
const untrustedConfigurations = this.configurationService.getUntrustedConfigurations();
if (untrustedConfigurations.length) {
const markerData: IMarkerData[] = [];
for (const untrustedConfiguration of untrustedConfigurations) {
const setting = this.workspaceSettingsEditorModel.getPreference(untrustedConfiguration);
if (setting) {
markerData.push({
severity: Severity.Error,
startLineNumber: setting.keyRange.startLineNumber,
startColumn: setting.keyRange.startColumn,
endLineNumber: setting.keyRange.endLineNumber,
endColumn: setting.keyRange.endColumn,
message: nls.localize('untrustedWorkspaceWithExectuables', "`{0}` specifies an executable. It is ignored because the workspace is untrusted. To mark a workspace as trusted, open the User settings and add workspace folder to `security.workspacesTrustedToSpecifyExecutables`", setting.key)
});
}
}
this.markerService.changeOne('preferencesEditor', this.workspaceSettingsEditorModel.uri, markerData);
}
}
public dispose(): void {
this.markerService.remove('preferencesEditor', [this.workspaceSettingsEditorModel.uri]);
super.dispose();
}
}
const DefaultSettingsEditorCommand = EditorCommand.bindToContribution<PreferencesEditorContribution<ISetting>>((editor: editorCommon.ICommonCodeEditor) => <PreferencesEditorContribution<ISetting>>editor.getContribution(DefaultSettingsEditorContribution.ID));
CommonEditorRegistry.registerEditorCommand(new DefaultSettingsEditorCommand({
......
......@@ -38,7 +38,8 @@ configurationRegistry.registerConfiguration({
'terminal.integrated.shell.linux': {
'description': nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux."),
'type': 'string',
'default': TERMINAL_DEFAULT_SHELL_LINUX
'default': TERMINAL_DEFAULT_SHELL_LINUX,
'isExecutable': true
},
'terminal.integrated.shellArgs.linux': {
'description': nls.localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal."),
......@@ -46,12 +47,14 @@ configurationRegistry.registerConfiguration({
'items': {
'type': 'string'
},
'default': []
'default': [],
'isExecutable': true
},
'terminal.integrated.shell.osx': {
'description': nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on OS X."),
'type': 'string',
'default': TERMINAL_DEFAULT_SHELL_OSX
'default': TERMINAL_DEFAULT_SHELL_OSX,
'isExecutable': true
},
'terminal.integrated.shellArgs.osx': {
'description': nls.localize('terminal.integrated.shellArgs.osx', "The command line arguments to use when on the OS X terminal."),
......@@ -59,12 +62,14 @@ configurationRegistry.registerConfiguration({
'items': {
'type': 'string'
},
'default': []
'default': [],
'isExecutable': true
},
'terminal.integrated.shell.windows': {
'description': nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows. When using shells shipped with Windows (cmd, PowerShell or Bash on Ubuntu), prefer C:\\Windows\\sysnative over C:\\Windows\\System32 to use the 64-bit versions."),
'type': 'string',
'default': TERMINAL_DEFAULT_SHELL_WINDOWS
'default': TERMINAL_DEFAULT_SHELL_WINDOWS,
'isExecutable': true
},
'terminal.integrated.shellArgs.windows': {
'description': nls.localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal."),
......@@ -72,7 +77,8 @@ configurationRegistry.registerConfiguration({
'items': {
'type': 'string'
},
'default': []
'default': [],
'isExecutable': true
},
'terminal.integrated.rightClickCopyPaste': {
'description': nls.localize('terminal.integrated.rightClickCopyPaste', "When set, this will prevent the context menu from appearing when right clicking within the terminal, instead it will copy when there is a selection and paste when there is no selection."),
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/platform';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import baseplatform = require('vs/base/common/platform');
class TrustContribution implements IWorkbenchContribution {
private toDispose: IDisposable[] = [];
private isUntrusted = false;
constructor(
@ILifecycleService lifecycleService: ILifecycleService,
@IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService,
@IPreferencesService private preferencesService: IPreferencesService,
@IMessageService private messageService: IMessageService,
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
@IConfigurationService private configurationService: IConfigurationService,
@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService
) {
lifecycleService.onShutdown(this.dispose, this);
this.toDispose.push(this.workspaceConfigurationService.onDidUpdateConfiguration(e => this.checkWorkspaceTrust()));
this.checkWorkspaceTrust();
}
getId(): string {
return 'trust';
}
public dispose(): void {
this.toDispose = dispose(this.toDispose);
}
private checkWorkspaceTrust(): void {
const wasUntrusted = this.isUntrusted;
this.isUntrusted = this.workspaceConfigurationService.getUntrustedConfigurations().length > 0;
if (this.isUntrusted && !wasUntrusted) {
this.showTrustWarning();
}
}
private getWorkspaceTrustKey(): string {
let path = this.workspaceContextService.getWorkspace().resource.fsPath;
if (baseplatform.isWindows && path.length > 2) {
if (path.charAt(1) === ':') {
return path.charAt(0).toLocaleUpperCase().concat(path.substr(1));
}
}
return path;
}
private updateUserSettings(): TPromise<void> {
const key = 'security.workspacesTrustedToSpecifyExecutables';
const workspace = this.getWorkspaceTrustKey();
const value = this.configurationService.lookup(key).user || {};
value[workspace] = true;
return this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: key, value: value }, { writeToBuffer: true, autoSave: false });
}
private showTrustWarning(): void {
const message = nls.localize('untrustedWorkspace', "This workspace specifies executables. While the workspace is untrusted, these settings are being ignored.");
const openWorkspaceSettings = new Action('trust.openWorkspaceSettings', nls.localize('openWorkspaceSettings', 'Review Settings'), '', true, () => {
return this.preferencesService.openWorkspaceSettings().then(() => false);
});
const trustWorkspace = new Action('trust.trustWorkspace', nls.localize('trustWorkspace', 'Trust Workspace'), '', true, () => {
return this.updateUserSettings().then(() => this.preferencesService.openGlobalSettings());
});
const noChange = new Action('trust.noChange', nls.localize('noChange', 'Do Not Trust Workspace'), '', true, () => TPromise.as(true));
const actions = [openWorkspaceSettings, trustWorkspace, noChange];
this.messageService.show(Severity.Warning, { message, actions });
}
}
const workbenchRegistry = <IWorkbenchContributionsRegistry>Registry.as(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(TrustContribution);
......@@ -14,6 +14,18 @@ export const IWorkspaceConfigurationService = createDecorator<IWorkspaceConfigur
export type IWorkspaceConfigurationValues = { [key: string]: IWorkspaceConfigurationValue<any> };
export interface IWorkspaceTrust {
/**
* Returns iff the workspace is trusted by the user.
*/
isTrusted(): boolean;
/**
* Returns a hash of all known configuration keys that can be used to specify executables.
*/
allKnownConfigKeysForExecutables(): { [configKey: string]: any };
}
export interface IWorkspaceConfigurationService extends IConfigurationService {
/**
......@@ -21,6 +33,11 @@ export interface IWorkspaceConfigurationService extends IConfigurationService {
*/
hasWorkspaceConfiguration(): boolean;
/**
* Returns untrusted configuration keys for the current workspace
*/
getUntrustedConfigurations(): string[];
/**
* Override for the IConfigurationService#lookup() method that adds information about workspace settings.
*/
......
......@@ -5,8 +5,7 @@
'use strict';
import { ConfigModel } from 'vs/platform/configuration/common/model';
import { IConfigModel } from 'vs/platform/configuration/common/configuration';
import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration';
import { IWorkspaceTrust, WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration';
export class ScopedConfigModel<T> extends ConfigModel<T> {
......@@ -24,14 +23,51 @@ export class ScopedConfigModel<T> extends ConfigModel<T> {
}
export class TrustedWorkspaceSettingsConfigModel<T> extends ConfigModel<T> {
private _untrustedKeys: string[] = [];
constructor(content: string, name: string = '', private workspaceTrust: IWorkspaceTrust = null) {
super(null, name);
if (content) {
this.update(content);
}
}
protected filterRaw(raw: any): { newRaw: any; removals: any } {
this._untrustedKeys = [];
let allUntrustedKeys = {};
if (this.workspaceTrust && !this.workspaceTrust.isTrusted()) {
allUntrustedKeys = this.workspaceTrust.allKnownConfigKeysForExecutables();
}
let trustedProperties: any = {};
for (let property in raw) {
if (!allUntrustedKeys[property]) {
trustedProperties[property] = raw[property];
} else {
this._untrustedKeys.push(property);
}
}
return trustedProperties;
}
public get untrustedKeys(): string[] {
return this._untrustedKeys;
}
}
export class WorkspaceConfigModel<T> extends ConfigModel<T> {
constructor(private workspaceSettingsConfig: IConfigModel<T>, private scopedConfigs: ScopedConfigModel<T>[]) {
constructor(private workspaceSettingsConfig: TrustedWorkspaceSettingsConfigModel<T>, private scopedConfigs: ScopedConfigModel<T>[]) {
super(null);
this.consolidate();
}
private consolidate(): void {
this._contents = <T>{};
this._overrides = [];
this.doMerge(this, this.workspaceSettingsConfig);
for (const configModel of this.scopedConfigs) {
this.doMerge(this, configModel);
......@@ -49,4 +85,16 @@ export class WorkspaceConfigModel<T> extends ConfigModel<T> {
});
return keys;
}
public refilter(): void {
this.workspaceSettingsConfig.refilter();
this.scopedConfigs.forEach(scopedConfigModel => {
scopedConfigModel.refilter();
});
this.consolidate();
}
public get untrustedKeys(): string[] {
return this.workspaceSettingsConfig.untrustedKeys;
}
}
\ No newline at end of file
......@@ -17,13 +17,17 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { readFile } from 'vs/base/node/pfs';
import errors = require('vs/base/common/errors');
import { ScopedConfigModel, WorkspaceConfigModel } from 'vs/workbench/services/configuration/common/model';
import { ScopedConfigModel, WorkspaceConfigModel, TrustedWorkspaceSettingsConfigModel } from 'vs/workbench/services/configuration/common/model';
import { IConfigurationServiceEvent, ConfigurationSource, getConfigurationValue, IConfigModel, IConfigurationOptions } from 'vs/platform/configuration/common/configuration';
import { ConfigModel } from 'vs/platform/configuration/common/model';
import { ConfigurationService as BaseConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { IWorkspaceConfigurationValues, IWorkspaceConfigurationService, IWorkspaceConfigurationValue, CONFIG_DEFAULT_NAME, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH } from 'vs/workbench/services/configuration/common/configuration';
import { IWorkspaceConfigurationValues, IWorkspaceConfigurationService, IWorkspaceTrust, IWorkspaceConfigurationValue, CONFIG_DEFAULT_NAME, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH } from 'vs/workbench/services/configuration/common/configuration';
import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files';
import Event, { Emitter } from 'vs/base/common/event';
import { Registry } from 'vs/platform/platform';
import { IConfigurationRegistry, IConfigurationNode, Extensions, ISecurityConfiguration } from 'vs/platform/configuration/common/configurationRegistry';
import baseplatform = require('vs/base/common/platform');
interface IStat {
resource: uri;
......@@ -41,6 +45,52 @@ interface IWorkspaceConfiguration<T> {
consolidated: any;
}
export class WorkspaceTrust implements IWorkspaceTrust {
constructor(private contextService: IWorkspaceContextService, private baseConfigurationService: BaseConfigurationService<any>) { }
private getWorkspaceTrustKey(): string {
const workspace = this.contextService.getWorkspace();
if (workspace) {
const path = workspace.resource.fsPath;
if (baseplatform.isWindows && path.length > 2) {
if (path.charAt(1) === ':') {
return path.charAt(0).toLocaleUpperCase().concat(path.substr(1));
}
}
return path;
}
return null;
}
public isTrusted(): boolean {
const workspaceTrustKey = this.getWorkspaceTrustKey();
if (workspaceTrustKey) {
const securityConfiguration = this.baseConfigurationService.getConfiguration<ISecurityConfiguration>();
const whiteList = securityConfiguration.security.workspacesTrustedToSpecifyExecutables;
return whiteList && whiteList[workspaceTrustKey];
}
return false;
}
public allKnownConfigKeysForExecutables(): { [key: string]: any } {
const configKeys: { [key: string]: boolean } = {};
const configurations: IConfigurationNode[] = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurations();
configurations.forEach((config) => {
const properties = config.properties;
if (properties) {
Object.keys(properties).map((key) => {
const property = properties[key];
if (property && property.isExecutable) {
configKeys[key] = true;
}
});
}
});
return configKeys;
}
}
/**
* Wraps around the basic configuration service and adds knowledge about workspace settings.
*/
......@@ -60,6 +110,8 @@ export class WorkspaceConfigurationService extends Disposable implements IWorksp
private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise<IConfigModel<any>> };
private reloadConfigurationScheduler: RunOnceScheduler;
private workspaceTrust: IWorkspaceTrust;
constructor(
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IEnvironmentService environmentService: IEnvironmentService,
......@@ -69,7 +121,7 @@ export class WorkspaceConfigurationService extends Disposable implements IWorksp
this.workspaceFilePathToConfiguration = Object.create(null);
this.cachedConfig = new ConfigModel<any>(null);
this.cachedWorkspaceConfig = new WorkspaceConfigModel(new ConfigModel(null), []);
this.cachedWorkspaceConfig = new WorkspaceConfigModel(new TrustedWorkspaceSettingsConfigModel(null), []);
this._onDidUpdateConfiguration = this._register(new Emitter<IConfigurationServiceEvent>());
......@@ -84,26 +136,30 @@ export class WorkspaceConfigurationService extends Disposable implements IWorksp
.done(null, errors.onUnexpectedError), WorkspaceConfigurationService.RELOAD_CONFIGURATION_DELAY));
this._register(this.baseConfigurationService.onDidUpdateConfiguration(e => this.onBaseConfigurationChanged(e)));
this.workspaceTrust = new WorkspaceTrust(this.contextService, this.baseConfigurationService);
}
get onDidUpdateConfiguration(): Event<IConfigurationServiceEvent> {
return this._onDidUpdateConfiguration.event;
}
private onBaseConfigurationChanged(e: IConfigurationServiceEvent): void {
private onBaseConfigurationChanged(event: IConfigurationServiceEvent): void {
this.cachedWorkspaceConfig.refilter();
// update cached config when base config changes
const newConfig = new ConfigModel<any>(null)
const configModel = new ConfigModel<any>(null)
.merge(this.baseConfigurationService.getCache().consolidated) // global/default values (do NOT modify)
.merge(this.cachedWorkspaceConfig); // workspace configured values
// emit this as update to listeners if changed
if (!objects.equals(this.cachedConfig.contents, newConfig.contents)) {
this.cachedConfig = newConfig;
if (!objects.equals(this.cachedConfig.contents, configModel.contents)) {
this.cachedConfig = configModel;
this._onDidUpdateConfiguration.fire({
config: this.cachedConfig.contents,
source: e.source,
sourceConfig: e.sourceConfig
source: event.source,
sourceConfig: event.sourceConfig
});
}
}
......@@ -186,7 +242,7 @@ export class WorkspaceConfigurationService extends Disposable implements IWorksp
return this.loadWorkspaceConfigFiles().then(workspaceConfigFiles => {
// Consolidate (support *.json files in the workspace settings folder)
let workspaceSettingsModel: IConfigModel<T> = <IConfigModel<T>>workspaceConfigFiles[WORKSPACE_CONFIG_DEFAULT_PATH] || new ConfigModel<T>(null);
let workspaceSettingsModel: TrustedWorkspaceSettingsConfigModel<T> = <TrustedWorkspaceSettingsConfigModel<T>>workspaceConfigFiles[WORKSPACE_CONFIG_DEFAULT_PATH] || new TrustedWorkspaceSettingsConfigModel<T>(null);
let otherConfigModels = Object.keys(workspaceConfigFiles).filter(key => key !== WORKSPACE_CONFIG_DEFAULT_PATH).map(key => <ScopedConfigModel<T>>workspaceConfigFiles[key]);
this.cachedWorkspaceConfig = new WorkspaceConfigModel<T>(workspaceSettingsModel, otherConfigModels);
......@@ -291,19 +347,23 @@ export class WorkspaceConfigurationService extends Disposable implements IWorksp
private createConfigModel<T>(content: IContent): IConfigModel<T> {
const path = this.contextService.toWorkspaceRelativePath(content.resource);
if (path === WORKSPACE_CONFIG_DEFAULT_PATH) {
return new ConfigModel<T>(content.value, content.resource.toString());
return new TrustedWorkspaceSettingsConfigModel<T>(content.value, content.resource.toString(), this.workspaceTrust);
} else {
const matches = /\/([^\.]*)*\.json/.exec(path);
if (matches && matches[1]) {
return new ScopedConfigModel<T>(content.value, content.resource.toString(), matches[1]);
}
}
return new ConfigModel<T>(null);
return new TrustedWorkspaceSettingsConfigModel<T>(null);
}
private isWorkspaceConfigurationFile(workspaceRelativePath: string): boolean {
return [WORKSPACE_CONFIG_DEFAULT_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS.launch, WORKSPACE_STANDALONE_CONFIGURATIONS.tasks].some(p => p === workspaceRelativePath);
}
public getUntrustedConfigurations(): string[] {
return this.cachedWorkspaceConfig.untrustedKeys;
}
}
// node.hs helper functions
......
......@@ -5,13 +5,12 @@
'use strict';
import * as assert from 'assert';
import { ConfigModel } from 'vs/platform/configuration/common/model';
import { WorkspaceConfigModel, ScopedConfigModel } from 'vs/workbench/services/configuration/common/model';
import { WorkspaceConfigModel, ScopedConfigModel, TrustedWorkspaceSettingsConfigModel } from 'vs/workbench/services/configuration/common/model';
suite('ConfigurationService - Model', () => {
test('Test consolidate (settings and tasks)', () => {
const settingsConfig = new ConfigModel(JSON.stringify({
const settingsConfig = new TrustedWorkspaceSettingsConfigModel(JSON.stringify({
awesome: true
}));
......@@ -30,7 +29,7 @@ suite('ConfigurationService - Model', () => {
});
test('Test consolidate (settings and launch)', () => {
const settingsConfig = new ConfigModel(JSON.stringify({
const settingsConfig = new TrustedWorkspaceSettingsConfigModel(JSON.stringify({
awesome: true
}));
......@@ -49,7 +48,7 @@ suite('ConfigurationService - Model', () => {
});
test('Test consolidate (settings and launch and tasks) - launch/tasks wins over settings file', () => {
const settingsConfig = new ConfigModel(JSON.stringify({
const settingsConfig = new TrustedWorkspaceSettingsConfigModel(JSON.stringify({
awesome: true,
launch: {
launchConfig: 'defined',
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册