diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 5993f0fddde..3988fd347ec 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -214,42 +214,53 @@ export class DefaultConfigurationModel extends ConfigurationModel { } } +export interface ConfigurationParseOptions { + scopes: ConfigurationScope[] | undefined; + excludeUnsafeConfigurations?: boolean; +} + export class ConfigurationModelParser { private _raw: any = null; private _configurationModel: ConfigurationModel | null = null; + private _excludedUnsafeConfigurations: string[] = []; private _parseErrors: any[] = []; - constructor(protected readonly _name: string, private _scopes?: ConfigurationScope[]) { } + constructor(protected readonly _name: string) { } get configurationModel(): ConfigurationModel { return this._configurationModel || new ConfigurationModel(); } + get excludedUnsafeConfigurations(): string[] { + return this._excludedUnsafeConfigurations; + } + get errors(): any[] { return this._parseErrors; } - public parseContent(content: string | null | undefined): void { + public parse(content: string | null | undefined, options?: ConfigurationParseOptions): void { if (!types.isUndefinedOrNull(content)) { const raw = this.doParseContent(content); - this.parseRaw(raw); + this.parseRaw(raw, options); } } - public parseRaw(raw: any): void { - this._raw = raw; - const configurationModel = this.doParseRaw(raw); - this._configurationModel = new ConfigurationModel(configurationModel.contents, configurationModel.keys, configurationModel.overrides); - } - - public parse(): void { + public reparse(options: ConfigurationParseOptions): void { if (this._raw) { - this.parseRaw(this._raw); + this.parseRaw(this._raw, options); } } - protected doParseContent(content: string): any { + public parseRaw(raw: any, options?: ConfigurationParseOptions): void { + this._raw = raw; + const { contents, keys, overrides, unsafeConfigurations } = this.doParseRaw(raw, options); + this._configurationModel = new ConfigurationModel(contents, keys, overrides); + this._excludedUnsafeConfigurations = unsafeConfigurations || []; + } + + private doParseContent(content: string): any { let raw: any = {}; let currentProperty: string | null = null; let currentParent: any = []; @@ -306,42 +317,49 @@ export class ConfigurationModelParser { return raw; } - protected doParseRaw(raw: any): IConfigurationModel { - if (this._scopes) { - const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - raw = this.filterByScope(raw, configurationProperties, true, this._scopes); - } + protected doParseRaw(raw: any, options?: ConfigurationParseOptions): IConfigurationModel & { unsafeConfigurations?: string[] } { + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const filtered = this.filter(raw, configurationProperties, true, options); + raw = filtered.raw; const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); const keys = Object.keys(raw); const overrides: IOverrides[] = toOverrides(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); - return { contents, keys, overrides }; + return { contents, keys, overrides, unsafeConfigurations: filtered.unsafeConfigurations }; } - private filterByScope(properties: any, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }, filterOverriddenProperties: boolean, scopes: ConfigurationScope[]): {} { - const result: any = {}; + private filter(properties: any, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema | undefined }, filterOverriddenProperties: boolean, options?: ConfigurationParseOptions): { raw: {}, unsafeConfigurations: string[] } { + if (!options?.scopes && !options?.excludeUnsafeConfigurations) { + return { raw: properties, unsafeConfigurations: [] }; + } + const raw: any = {}; + const unsafeConfigurations: string[] = []; for (let key in properties) { if (OVERRIDE_PROPERTY_PATTERN.test(key) && filterOverriddenProperties) { - result[key] = this.filterByScope(properties[key], configurationProperties, false, scopes); + const result = this.filter(properties[key], configurationProperties, false, options); + raw[key] = result.raw; + unsafeConfigurations.push(...result.unsafeConfigurations); } else { - const scope = this.getScope(key, configurationProperties); + const propertySchema = configurationProperties[key]; + const scope = propertySchema ? typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : ConfigurationScope.WINDOW : undefined; // Load unregistered configurations always. - if (scope === undefined || scopes.indexOf(scope) !== -1) { - result[key] = properties[key]; + if (scope === undefined || options.scopes === undefined || options.scopes.includes(scope)) { + if (options.excludeUnsafeConfigurations && propertySchema?.requiresTrustedWorkspace) { + unsafeConfigurations.push(key); + } else { + raw[key] = properties[key]; + } } } } - return result; + return { raw, unsafeConfigurations }; } - private getScope(key: string, configurationProperties: { [qualifiedKey: string]: IConfigurationPropertySchema }): ConfigurationScope | undefined { - const propertySchema = configurationProperties[key]; - return propertySchema ? typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : ConfigurationScope.WINDOW : undefined; - } } export class UserSettings extends Disposable { private readonly parser: ConfigurationModelParser; + private readonly parseOptions: ConfigurationParseOptions; protected readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; @@ -352,7 +370,8 @@ export class UserSettings extends Disposable { private readonly fileService: IFileService ) { super(); - this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes); + this.parser = new ConfigurationModelParser(this.userSettingsResource.toString()); + this.parseOptions = { scopes: this.scopes }; this._register(this.fileService.watch(extUri.dirname(this.userSettingsResource))); // Also listen to the resource incase the resource is a symlink - https://github.com/microsoft/vscode/issues/118134 this._register(this.fileService.watch(this.userSettingsResource)); @@ -362,15 +381,15 @@ export class UserSettings extends Disposable { async loadConfiguration(): Promise { try { const content = await this.fileService.readFile(this.userSettingsResource); - this.parser.parseContent(content.value.toString() || '{}'); + this.parser.parse(content.value.toString() || '{}', this.parseOptions); return this.parser.configurationModel; } catch (e) { return new ConfigurationModel(); } } - reprocess(): ConfigurationModel { - this.parser.parse(); + reparse(): ConfigurationModel { + this.parser.reparse(this.parseOptions); return this.parser.configurationModel; } } @@ -802,5 +821,4 @@ export class AllKeysConfigurationChangeEvent extends ConfigurationChangeEvent { constructor(configuration: Configuration, workspace: Workspace, public source: ConfigurationTarget, public sourceConfig: any) { super({ keys: configuration.allKeys(), overrides: [] }, undefined, configuration, workspace); } - } diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 7504cb721b3..d4a53e68587 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -110,6 +110,7 @@ export const enum ConfigurationScope { export interface IConfigurationPropertySchema extends IJSONSchema { scope?: ConfigurationScope; + requiresTrustedWorkspace?: boolean; included?: boolean; tags?: string[]; /** @@ -136,6 +137,7 @@ export interface IConfigurationNode { properties?: { [path: string]: IConfigurationPropertySchema; }; allOf?: IConfigurationNode[]; scope?: ConfigurationScope; + requiresTrustedWorkspace?: boolean; extensionInfo?: IConfigurationExtensionInfo; } @@ -297,8 +299,9 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.updateOverridePropertyPatternKey(); } - private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW): string[] { + private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean = true, scope: ConfigurationScope = ConfigurationScope.WINDOW, requiresTrustedWorkspace?: boolean): string[] { scope = types.isUndefinedOrNull(configuration.scope) ? scope : configuration.scope; + requiresTrustedWorkspace = types.isUndefinedOrNull(configuration.requiresTrustedWorkspace) ? types.isUndefined(requiresTrustedWorkspace) ? false : requiresTrustedWorkspace : configuration.requiresTrustedWorkspace; let propertyKeys: string[] = []; let properties = configuration.properties; if (properties) { @@ -318,6 +321,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { property.scope = undefined; // No scope for overridable properties `[${identifier}]` } else { property.scope = types.isUndefinedOrNull(property.scope) ? scope : property.scope; + property.requiresTrustedWorkspace = types.isUndefinedOrNull(property.requiresTrustedWorkspace) ? requiresTrustedWorkspace : property.requiresTrustedWorkspace; } // Add to properties maps @@ -341,7 +345,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { let subNodes = configuration.allOf; if (subNodes) { for (let node of subNodes) { - propertyKeys.push(...this.validateAndRegisterProperties(node, validate, scope)); + propertyKeys.push(...this.validateAndRegisterProperties(node, validate, scope, requiresTrustedWorkspace)); } } return propertyKeys; diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 97b4a40cc91..544fd32e573 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -242,10 +242,10 @@ suite('CustomConfigurationModel', () => { test('simple merge using models', () => { let base = new ConfigurationModelParser('base'); - base.parseContent(JSON.stringify({ 'a': 1, 'b': 2 })); + base.parse(JSON.stringify({ 'a': 1, 'b': 2 })); let add = new ConfigurationModelParser('add'); - add.parseContent(JSON.stringify({ 'a': 3, 'c': 4 })); + add.parse(JSON.stringify({ 'a': 3, 'c': 4 })); let result = base.configurationModel.merge(add.configurationModel); assert.deepStrictEqual(result.contents, { 'a': 3, 'b': 2, 'c': 4 }); @@ -253,14 +253,14 @@ suite('CustomConfigurationModel', () => { test('simple merge with an undefined contents', () => { let base = new ConfigurationModelParser('base'); - base.parseContent(JSON.stringify({ 'a': 1, 'b': 2 })); + base.parse(JSON.stringify({ 'a': 1, 'b': 2 })); let add = new ConfigurationModelParser('add'); let result = base.configurationModel.merge(add.configurationModel); assert.deepStrictEqual(result.contents, { 'a': 1, 'b': 2 }); base = new ConfigurationModelParser('base'); add = new ConfigurationModelParser('add'); - add.parseContent(JSON.stringify({ 'a': 1, 'b': 2 })); + add.parse(JSON.stringify({ 'a': 1, 'b': 2 })); result = base.configurationModel.merge(add.configurationModel); assert.deepStrictEqual(result.contents, { 'a': 1, 'b': 2 }); @@ -272,25 +272,25 @@ suite('CustomConfigurationModel', () => { test('Recursive merge using config models', () => { let base = new ConfigurationModelParser('base'); - base.parseContent(JSON.stringify({ 'a': { 'b': 1 } })); + base.parse(JSON.stringify({ 'a': { 'b': 1 } })); let add = new ConfigurationModelParser('add'); - add.parseContent(JSON.stringify({ 'a': { 'b': 2 } })); + add.parse(JSON.stringify({ 'a': { 'b': 2 } })); let result = base.configurationModel.merge(add.configurationModel); assert.deepStrictEqual(result.contents, { 'a': { 'b': 2 } }); }); test('Test contents while getting an existing property', () => { let testObject = new ConfigurationModelParser('test'); - testObject.parseContent(JSON.stringify({ 'a': 1 })); + testObject.parse(JSON.stringify({ 'a': 1 })); assert.deepStrictEqual(testObject.configurationModel.getValue('a'), 1); - testObject.parseContent(JSON.stringify({ 'a': { 'b': 1 } })); + testObject.parse(JSON.stringify({ 'a': { 'b': 1 } })); assert.deepStrictEqual(testObject.configurationModel.getValue('a'), { 'b': 1 }); }); test('Test contents are undefined for non existing properties', () => { const testObject = new ConfigurationModelParser('test'); - testObject.parseContent(JSON.stringify({ + testObject.parse(JSON.stringify({ awesome: true })); @@ -305,7 +305,7 @@ suite('CustomConfigurationModel', () => { test('Test configWithOverrides gives all content merged with overrides', () => { const testObject = new ConfigurationModelParser('test'); - testObject.parseContent(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } })); + testObject.parse(JSON.stringify({ 'a': 1, 'c': 1, '[b]': { 'a': 2 } })); assert.deepStrictEqual(testObject.configurationModel.override('b').contents, { 'a': 2, 'c': 1, '[b]': { 'a': 2 } }); }); @@ -318,17 +318,17 @@ suite('CustomConfigurationModel', () => { test('Test update with empty data', () => { const testObject = new ConfigurationModelParser('test'); - testObject.parseContent(''); + testObject.parse(''); assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null)); assert.deepStrictEqual(testObject.configurationModel.keys, []); - testObject.parseContent(null!); + testObject.parse(null!); assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null)); assert.deepStrictEqual(testObject.configurationModel.keys, []); - testObject.parseContent(undefined!); + testObject.parse(undefined!); assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null)); assert.deepStrictEqual(testObject.configurationModel.keys, []); @@ -380,7 +380,7 @@ suite('Configuration', () => { test('Test update value', () => { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify({ 'a': 1 })); + parser.parse(JSON.stringify({ 'a': 1 })); const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); testObject.updateValue('a', 2); @@ -390,7 +390,7 @@ suite('Configuration', () => { test('Test update value after inspect', () => { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify({ 'a': 1 })); + parser.parse(JSON.stringify({ 'a': 1 })); const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); testObject.inspect('a', {}, undefined); @@ -503,7 +503,7 @@ suite('Configuration', () => { function parseConfigurationModel(content: any): ConfigurationModel { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify(content)); + parser.parse(JSON.stringify(content)); return parser.configurationModel; } @@ -951,6 +951,6 @@ suite('AllKeysConfigurationChangeEvent', () => { function toConfigurationModel(obj: any): ConfigurationModel { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify(obj)); + parser.parse(JSON.stringify(obj)); return parser.configurationModel; } diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 21c2fb7b350..a07dbd301ad 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -181,10 +181,10 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { for (let extension of added) { const configurations: IConfigurationNode[] = []; const value = extension.value; - if (!Array.isArray(value)) { - configurations.push(...handleConfiguration(value, extension)); - } else { + if (Array.isArray(value)) { value.forEach(v => configurations.push(...handleConfiguration(v, extension))); + } else { + configurations.push(...handleConfiguration(value, extension)); } extensionConfigurations.set(ExtensionIdentifier.toKey(extension.description.identifier), configurations); addedConfigurations.push(...configurations); diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index d4ce3c91f3f..b084476d31d 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -9,7 +9,7 @@ import * as errors from 'vs/base/common/errors'; import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { ConfigurationModel, ConfigurationModelParser, UserSettings } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -23,6 +23,11 @@ import { hash } from 'vs/base/common/hash'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { ILogService } from 'vs/platform/log/common/log'; import { IStringDictionary } from 'vs/base/common/collections'; +import { WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; + +function hasToExcludeUnsafeConfigurations(workspaceTrustState: WorkspaceTrustState): boolean { + return workspaceTrustState !== WorkspaceTrustState.Trusted; +} export class UserConfiguration extends Disposable { @@ -32,17 +37,20 @@ export class UserConfiguration extends Disposable { private readonly userConfiguration: MutableDisposable = this._register(new MutableDisposable()); private readonly reloadConfigurationScheduler: RunOnceScheduler; + private readonly configurationParseOptions: ConfigurationParseOptions; + get hasTasksLoaded(): boolean { return this.userConfiguration.value instanceof FileServiceBasedConfiguration; } constructor( private readonly userSettingsResource: URI, - private readonly scopes: ConfigurationScope[] | undefined, + scopes: ConfigurationScope[] | undefined, private readonly fileService: IFileService, private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, ) { super(); - this.userConfiguration.value = new UserSettings(this.userSettingsResource, this.scopes, uriIdentityService.extUri, this.fileService); + this.configurationParseOptions = { scopes, excludeUnsafeConfigurations: false }; + this.userConfiguration.value = new UserSettings(this.userSettingsResource, scopes, uriIdentityService.extUri, this.fileService); this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule())); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); } @@ -58,7 +66,7 @@ export class UserConfiguration extends Disposable { const folder = this.uriIdentityService.extUri.dirname(this.userSettingsResource); const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY].map(name => ([name, this.uriIdentityService.extUri.joinPath(folder, `${name}.json`)])); - const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), this.userSettingsResource, standAloneConfigurationResources, this.scopes, this.fileService, this.uriIdentityService, this.logService); + const fileServiceBasedConfiguration = new FileServiceBasedConfiguration(folder.toString(), this.userSettingsResource, standAloneConfigurationResources, this.configurationParseOptions, this.fileService, this.uriIdentityService, this.logService); const configurationModel = await fileServiceBasedConfiguration.loadConfiguration(); this.userConfiguration.value = fileServiceBasedConfiguration; @@ -70,8 +78,8 @@ export class UserConfiguration extends Disposable { return configurationModel; } - reprocess(): ConfigurationModel { - return this.userConfiguration.value!.reprocess(); + reparse(): ConfigurationModel { + return this.userConfiguration.value!.reparse(this.configurationParseOptions); } } @@ -79,6 +87,7 @@ class FileServiceBasedConfiguration extends Disposable { private readonly allResources: URI[]; private _folderSettingsModelParser: ConfigurationModelParser; + private _folderSettingsParseOptions: ConfigurationParseOptions; private _standAloneConfigurations: ConfigurationModel[]; private _cache: ConfigurationModel; @@ -89,7 +98,7 @@ class FileServiceBasedConfiguration extends Disposable { name: string, private readonly settingsResource: URI, private readonly standAloneConfigurationResources: [string, URI][], - private readonly scopes: ConfigurationScope[] | undefined, + configurationParseOptions: ConfigurationParseOptions, private readonly fileService: IFileService, private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, @@ -102,7 +111,8 @@ class FileServiceBasedConfiguration extends Disposable { this.fileService.watch(resource) )))); - this._folderSettingsModelParser = new ConfigurationModelParser(name, this.scopes); + this._folderSettingsModelParser = new ConfigurationModelParser(name); + this._folderSettingsParseOptions = configurationParseOptions; this._standAloneConfigurations = []; this._cache = new ConfigurationModel(); @@ -144,17 +154,17 @@ class FileServiceBasedConfiguration extends Disposable { // reset this._standAloneConfigurations = []; - this._folderSettingsModelParser.parseContent(''); + this._folderSettingsModelParser.parse('', this._folderSettingsParseOptions); // parse if (settingsContent !== undefined) { - this._folderSettingsModelParser.parseContent(settingsContent); + this._folderSettingsModelParser.parse(settingsContent, this._folderSettingsParseOptions); } for (let index = 0; index < standAloneConfigurationContents.length; index++) { const contents = standAloneConfigurationContents[index][1]; if (contents !== undefined) { const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.standAloneConfigurationResources[index][1].toString(), this.standAloneConfigurationResources[index][0]); - standAloneConfigurationModelParser.parseContent(contents); + standAloneConfigurationModelParser.parse(contents); this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel); } } @@ -165,9 +175,14 @@ class FileServiceBasedConfiguration extends Disposable { return this._cache; } - reprocess(): ConfigurationModel { + getExcludedUnsafeSettings(): string[] { + return this._folderSettingsModelParser.excludedUnsafeConfigurations; + } + + reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel { const oldContents = this._folderSettingsModelParser.configurationModel.contents; - this._folderSettingsModelParser.parse(); + this._folderSettingsParseOptions = configurationParseOptions; + this._folderSettingsModelParser.reparse(this._folderSettingsParseOptions); if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) { this.consolidate(); } @@ -249,8 +264,8 @@ export class RemoteUserConfiguration extends Disposable { return this._userConfiguration.reload(); } - reprocess(): ConfigurationModel { - return this._userConfiguration.reprocess(); + reparse(): ConfigurationModel { + return this._userConfiguration.reparse(); } private onDidUserConfigurationChange(configurationModel: ConfigurationModel): void { @@ -277,6 +292,7 @@ export class RemoteUserConfiguration extends Disposable { class FileServiceBasedRemoteUserConfiguration extends Disposable { private readonly parser: ConfigurationModelParser; + private readonly parseOptions: ConfigurationParseOptions; private readonly reloadConfigurationScheduler: RunOnceScheduler; protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; @@ -286,13 +302,14 @@ class FileServiceBasedRemoteUserConfiguration extends Disposable { constructor( private readonly configurationResource: URI, - private readonly scopes: ConfigurationScope[] | undefined, + scopes: ConfigurationScope[] | undefined, private readonly fileService: IFileService, private readonly uriIdentityService: IUriIdentityService, ) { super(); - this.parser = new ConfigurationModelParser(this.configurationResource.toString(), this.scopes); + this.parser = new ConfigurationModelParser(this.configurationResource.toString()); + this.parseOptions = { scopes }; this._register(fileService.onDidFilesChange(e => this.handleFileEvents(e))); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); this._register(toDisposable(() => { @@ -334,15 +351,15 @@ class FileServiceBasedRemoteUserConfiguration extends Disposable { async reload(): Promise { try { const content = await this.resolveContent(); - this.parser.parseContent(content); + this.parser.parse(content, this.parseOptions); return this.parser.configurationModel; } catch (e) { return new ConfigurationModel(); } } - reprocess(): ConfigurationModel { - this.parser.parse(); + reparse(): ConfigurationModel { + this.parser.reparse(this.parseOptions); return this.parser.configurationModel; } @@ -381,6 +398,7 @@ class CachedRemoteUserConfiguration extends Disposable { private readonly key: ConfigurationKey; private readonly parser: ConfigurationModelParser; + private readonly parseOptions: ConfigurationParseOptions; private configurationModel: ConfigurationModel; constructor( @@ -390,7 +408,8 @@ class CachedRemoteUserConfiguration extends Disposable { ) { super(); this.key = { type: 'user', key: remoteAuthority }; - this.parser = new ConfigurationModelParser('CachedRemoteUserConfiguration', scopes); + this.parser = new ConfigurationModelParser('CachedRemoteUserConfiguration'); + this.parseOptions = { scopes }; this.configurationModel = new ConfigurationModel(); } @@ -402,8 +421,8 @@ class CachedRemoteUserConfiguration extends Disposable { return this.reload(); } - reprocess(): ConfigurationModel { - this.parser.parse(); + reparse(): ConfigurationModel { + this.parser.reparse(this.parseOptions); this.configurationModel = this.parser.configurationModel; return this.configurationModel; } @@ -413,7 +432,7 @@ class CachedRemoteUserConfiguration extends Disposable { const content = await this.configurationCache.read(this.key); const parsed: { content: string } = JSON.parse(content); if (parsed.content) { - this.parser.parseContent(parsed.content); + this.parser.parse(parsed.content, this.parseOptions); this.configurationModel = this.parser.configurationModel; } } catch (e) { /* Ignore error */ } @@ -436,6 +455,7 @@ export class WorkspaceConfiguration extends Disposable { private _workspaceConfiguration: CachedWorkspaceConfiguration | FileServiceBasedWorkspaceConfiguration; private _workspaceConfigurationDisposables = this._register(new DisposableStore()); private _workspaceIdentifier: IWorkspaceIdentifier | null = null; + private _workspaceTrustState: WorkspaceTrustState | null = null; private readonly _onDidUpdateConfiguration: Emitter = this._register(new Emitter()); public readonly onDidUpdateConfiguration: Event = this._onDidUpdateConfiguration.event; @@ -451,8 +471,9 @@ export class WorkspaceConfiguration extends Disposable { this._workspaceConfiguration = this._cachedConfiguration = new CachedWorkspaceConfiguration(configurationCache); } - async initialize(workspaceIdentifier: IWorkspaceIdentifier): Promise { + async initialize(workspaceIdentifier: IWorkspaceIdentifier, workspaceTrustState: WorkspaceTrustState): Promise { this._workspaceIdentifier = workspaceIdentifier; + this._workspaceTrustState = workspaceTrustState; if (!this._initialized) { if (this.configurationCache.needsCaching(this._workspaceIdentifier.configPath)) { this._workspaceConfiguration = this._cachedConfiguration; @@ -466,7 +487,7 @@ export class WorkspaceConfiguration extends Disposable { async reload(): Promise { if (this._workspaceIdentifier) { - await this._workspaceConfiguration.load(this._workspaceIdentifier); + await this._workspaceConfiguration.load(this._workspaceIdentifier, { scopes: WORKSPACE_SCOPES, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() }); } } @@ -486,16 +507,25 @@ export class WorkspaceConfiguration extends Disposable { return this._workspaceConfiguration.getWorkspaceSettings(); } - reprocessWorkspaceSettings(): ConfigurationModel { - this._workspaceConfiguration.reprocessWorkspaceSettings(); + updateWorkspaceTrustState(workspaceTrustState: WorkspaceTrustState): ConfigurationModel { + this._workspaceTrustState = workspaceTrustState; + return this.reparseWorkspaceSettings(); + } + + reparseWorkspaceSettings(): ConfigurationModel { + this._workspaceConfiguration.reparseWorkspaceSettings({ scopes: WORKSPACE_SCOPES, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() }); return this.getConfiguration(); } + getExcludedUnsafeSettings(): string[] { + return this._workspaceConfiguration.getExcludedUnsafeSettings(); + } + private async waitAndInitialize(workspaceIdentifier: IWorkspaceIdentifier): Promise { await whenProviderRegistered(workspaceIdentifier.configPath, this._fileService); if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { const fileServiceBasedWorkspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this._fileService)); - await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier); + await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier, { scopes: WORKSPACE_SCOPES, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() }); this.doInitialize(fileServiceBasedWorkspaceConfiguration); this.onDidWorkspaceConfigurationChange(false); } @@ -508,6 +538,10 @@ export class WorkspaceConfiguration extends Disposable { this._initialized = true; } + private hasToExcludeUnsafeConfigurations(): boolean | undefined { + return this._workspaceTrustState !== null ? hasToExcludeUnsafeConfigurations(this._workspaceTrustState) : undefined; + } + private async onDidWorkspaceConfigurationChange(reload: boolean): Promise { if (reload) { await this.reload(); @@ -555,7 +589,7 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable { return content.value.toString(); } - async load(workspaceIdentifier: IWorkspaceIdentifier): Promise { + async load(workspaceIdentifier: IWorkspaceIdentifier, configurationParseOptions: ConfigurationParseOptions): Promise { if (!this._workspaceIdentifier || this._workspaceIdentifier.id !== workspaceIdentifier.id) { this._workspaceIdentifier = workspaceIdentifier; this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this._workspaceIdentifier.id); @@ -571,7 +605,7 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable { errors.onUnexpectedError(error); } } - this.workspaceConfigurationModelParser.parseContent(contents); + this.workspaceConfigurationModelParser.parse(contents, configurationParseOptions); this.consolidate(); } @@ -587,12 +621,16 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable { return this.workspaceSettings; } - reprocessWorkspaceSettings(): ConfigurationModel { - this.workspaceConfigurationModelParser.reprocessWorkspaceSettings(); + reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel { + this.workspaceConfigurationModelParser.reparseWorkspaceSettings(configurationParseOptions); this.consolidate(); return this.getWorkspaceSettings(); } + getExcludedUnsafeSettings(): string[] { + return this.workspaceConfigurationModelParser.excludedUnsafeConfigurations; + } + private consolidate(): void { this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel); } @@ -624,14 +662,14 @@ class CachedWorkspaceConfiguration { this.workspaceSettings = new ConfigurationModel(); } - async load(workspaceIdentifier: IWorkspaceIdentifier): Promise { + async load(workspaceIdentifier: IWorkspaceIdentifier, configurationParseOptions: ConfigurationParseOptions): Promise { try { const key = this.getKey(workspaceIdentifier); const contents = await this.configurationCache.read(key); const parsed: { content: string } = JSON.parse(contents); if (parsed.content) { this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(key.key); - this.workspaceConfigurationModelParser.parseContent(parsed.content); + this.workspaceConfigurationModelParser.parse(parsed.content, configurationParseOptions); this.consolidate(); } } catch (e) { @@ -654,12 +692,16 @@ class CachedWorkspaceConfiguration { return this.workspaceSettings; } - reprocessWorkspaceSettings(): ConfigurationModel { - this.workspaceConfigurationModelParser.reprocessWorkspaceSettings(); + reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel { + this.workspaceConfigurationModelParser.reparseWorkspaceSettings(configurationParseOptions); this.consolidate(); return this.getWorkspaceSettings(); } + getExcludedUnsafeSettings(): string[] { + return this.workspaceConfigurationModelParser.excludedUnsafeConfigurations; + } + private consolidate(): void { this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel, this.workspaceConfigurationModelParser.tasksModel); } @@ -689,6 +731,7 @@ class CachedFolderConfiguration { readonly onDidChange = Event.None; private _folderSettingsModelParser: ConfigurationModelParser; + private _folderSettingsParseOptions: ConfigurationParseOptions; private _standAloneConfigurations: ConfigurationModel[]; private configurationModel: ConfigurationModel; private readonly key: ConfigurationKey; @@ -696,11 +739,12 @@ class CachedFolderConfiguration { constructor( folder: URI, configFolderRelativePath: string, + configurationParseOptions: ConfigurationParseOptions, private readonly configurationCache: IConfigurationCache, - scopes: ConfigurationScope[], ) { this.key = { type: 'folder', key: hash(join(folder.path, configFolderRelativePath)).toString(16) }; - this._folderSettingsModelParser = new ConfigurationModelParser('CachedFolderConfiguration', scopes); + this._folderSettingsModelParser = new ConfigurationModelParser('CachedFolderConfiguration'); + this._folderSettingsParseOptions = configurationParseOptions; this._standAloneConfigurations = []; this.configurationModel = new ConfigurationModel(); } @@ -712,10 +756,10 @@ class CachedFolderConfiguration { if (configurationContents) { for (const key of Object.keys(configurationContents)) { if (key === FOLDER_SETTINGS_NAME) { - this._folderSettingsModelParser.parseContent(configurationContents[key]); + this._folderSettingsModelParser.parse(configurationContents[key], this._folderSettingsParseOptions); } else { const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(key, key); - standAloneConfigurationModelParser.parseContent(configurationContents[key]); + standAloneConfigurationModelParser.parse(configurationContents[key]); this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel); } } @@ -743,8 +787,13 @@ class CachedFolderConfiguration { } } - reprocess(): ConfigurationModel { - this._folderSettingsModelParser.parse(); + getExcludedUnsafeSettings(): string[] { + return this._folderSettingsModelParser.excludedUnsafeConfigurations; + } + + reparse(configurationParseOptions: ConfigurationParseOptions): ConfigurationModel { + this._folderSettingsParseOptions = configurationParseOptions; + this._folderSettingsModelParser.reparse(this._folderSettingsParseOptions); this.consolidate(); return this.configurationModel; } @@ -764,6 +813,7 @@ export class FolderConfiguration extends Disposable { readonly onDidChange: Event = this._onDidChange.event; private folderConfiguration: CachedFolderConfiguration | FileServiceBasedConfiguration; + private readonly scopes: ConfigurationScope[]; private readonly configurationFolder: URI; private cachedFolderConfiguration: CachedFolderConfiguration; @@ -771,6 +821,7 @@ export class FolderConfiguration extends Disposable { readonly workspaceFolder: IWorkspaceFolder, configFolderRelativePath: string, private readonly workbenchState: WorkbenchState, + private workspaceTrustState: WorkspaceTrustState, fileService: IFileService, uriIdentityService: IUriIdentityService, logService: ILogService, @@ -778,19 +829,19 @@ export class FolderConfiguration extends Disposable { ) { super(); - const scopes = WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES; + this.scopes = WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES; this.configurationFolder = uriIdentityService.extUri.joinPath(workspaceFolder.uri, configFolderRelativePath); - this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, configurationCache, scopes); + this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, { scopes: this.scopes, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() }, configurationCache); if (this.configurationCache.needsCaching(workspaceFolder.uri)) { this.folderConfiguration = this.cachedFolderConfiguration; whenProviderRegistered(workspaceFolder.uri, fileService) .then(() => { - this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(scopes, fileService, uriIdentityService, logService)); + this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService)); this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); this.onDidFolderConfigurationChange(); }); } else { - this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(scopes, fileService, uriIdentityService, logService)); + this.folderConfiguration = this._register(this.createFileServiceBasedConfiguration(fileService, uriIdentityService, logService)); this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange())); } } @@ -799,21 +850,34 @@ export class FolderConfiguration extends Disposable { return this.folderConfiguration.loadConfiguration(); } - reprocess(): ConfigurationModel { - const configurationModel = this.folderConfiguration.reprocess(); + updateWorkspaceTrustState(workspaceTrustState: WorkspaceTrustState): ConfigurationModel { + this.workspaceTrustState = workspaceTrustState; + return this.reparse(); + } + + reparse(): ConfigurationModel { + const configurationModel = this.folderConfiguration.reparse({ scopes: this.scopes, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() }); this.updateCache(); return configurationModel; } + getExcludedUnsafeSettings(): string[] { + return this.folderConfiguration.getExcludedUnsafeSettings(); + } + + private hasToExcludeUnsafeConfigurations(): boolean { + return hasToExcludeUnsafeConfigurations(this.workspaceTrustState); + } + private onDidFolderConfigurationChange(): void { this.updateCache(); this._onDidChange.fire(); } - private createFileServiceBasedConfiguration(scopes: ConfigurationScope[], fileService: IFileService, uriIdentityService: IUriIdentityService, logService: ILogService) { + private createFileServiceBasedConfiguration(fileService: IFileService, uriIdentityService: IUriIdentityService, logService: ILogService) { const settingsResource = uriIdentityService.extUri.joinPath(this.configurationFolder, `${FOLDER_SETTINGS_NAME}.json`); const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY].map(name => ([name, uriIdentityService.extUri.joinPath(this.configurationFolder, `${name}.json`)])); - return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResource, standAloneConfigurationResources, scopes, fileService, uriIdentityService, logService); + return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResource, standAloneConfigurationResources, { scopes: this.scopes, excludeUnsafeConfigurations: this.hasToExcludeUnsafeConfigurations() }, fileService, uriIdentityService, logService); } private async updateCache(): Promise { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index cd98feb38fe..f90fcf97780 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -12,7 +12,7 @@ import { Queue, Barrier, runWhenIdle, Promises } from 'vs/base/common/async'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; -import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -32,6 +32,8 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { ILogService } from 'vs/platform/log/common/log'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IWorkspaceTrustService, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; +import { delta, distinct } from 'vs/base/common/arrays'; class Workspace extends BaseWorkspace { initialized: boolean = false; @@ -58,18 +60,25 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private readonly fileService: IFileService; private readonly uriIdentityService: IUriIdentityService; - protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; - protected readonly _onDidChangeWorkspaceFolders: Emitter = this._register(new Emitter()); + private readonly _onDidChangeWorkspaceFolders: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkspaceFolders: Event = this._onDidChangeWorkspaceFolders.event; - protected readonly _onDidChangeWorkspaceName: Emitter = this._register(new Emitter()); + private readonly _onDidChangeWorkspaceName: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkspaceName: Event = this._onDidChangeWorkspaceName.event; - protected readonly _onDidChangeWorkbenchState: Emitter = this._register(new Emitter()); + private readonly _onDidChangeWorkbenchState: Emitter = this._register(new Emitter()); public readonly onDidChangeWorkbenchState: Event = this._onDidChangeWorkbenchState.event; + private readonly _onDidChangeUnsafeWorkspaceSettings = this._register(new Emitter>()); + public readonly onDidChangeUnsafeWorkspaceSettings = this._onDidChangeUnsafeWorkspaceSettings.event; + + private workspaceTrustState: WorkspaceTrustState = WorkspaceTrustState.Trusted; + private _unSafeWorkspaceSettings: string[] = []; + get unSafeWorkspaceSettings() { return this._unSafeWorkspaceSettings; } + private readonly configurationRegistry: IConfigurationRegistry; // TODO@sandeep debt with cyclic dependencies @@ -390,6 +399,34 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat mark('code/didInitWorkspaceService'); } + updateWorkspaceTrustState(workspaceTrustState: WorkspaceTrustState): void { + if (this.workspaceTrustState !== workspaceTrustState) { + this.workspaceTrustState = workspaceTrustState; + const data = this._configuration.toData(); + const folderConfigurationModels: (ConfigurationModel | undefined)[] = []; + for (const folder of this.workspace.folders) { + const folderConfiguration = this.cachedFolderConfigs.get(folder.uri); + let configurationModel: ConfigurationModel | undefined; + if (folderConfiguration) { + configurationModel = folderConfiguration.updateWorkspaceTrustState(this.workspaceTrustState); + this._configuration.updateFolderConfiguration(folder.uri, configurationModel); + } + folderConfigurationModels.push(configurationModel); + } + if (this.getWorkbenchState() === WorkbenchState.FOLDER) { + if (folderConfigurationModels[0]) { + this._configuration.updateWorkspaceConfiguration(folderConfigurationModels[0]); + } + } else { + this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.updateWorkspaceTrustState(this.workspaceTrustState)); + } + const keys = this.updateUnsafeWorkpsaceSettings(); + if (keys.length) { + this.triggerConfigurationChange({ keys, overrides: [] }, { data, workspace: this.workspace }, ConfigurationTarget.WORKSPACE); + } + } + } + acquireInstantiationService(instantiationService: IInstantiationService): void { this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService); this.jsonEditingService = instantiationService.createInstance(JSONEditingService); @@ -401,7 +438,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } - private createWorkspace(arg: IWorkspaceInitializationPayload): Promise { + private async createWorkspace(arg: IWorkspaceInitializationPayload): Promise { if (isWorkspaceIdentifier(arg)) { return this.createMultiFolderWorkspace(arg); } @@ -413,22 +450,20 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return this.createEmptyWorkspace(arg); } - private createMultiFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): Promise { - return this.workspaceConfiguration.initialize({ id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath }) - .then(() => { - const workspaceConfigPath = workspaceIdentifier.configPath; - const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), workspaceConfigPath, this.uriIdentityService.extUri); - const workspaceId = workspaceIdentifier.id; - const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath, uri => this.uriIdentityService.extUri.ignorePathCasing(uri)); - workspace.initialized = this.workspaceConfiguration.initialized; - return workspace; - }); + private async createMultiFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): Promise { + await this.workspaceConfiguration.initialize({ id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath }, this.workspaceTrustState); + const workspaceConfigPath = workspaceIdentifier.configPath; + const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), workspaceConfigPath, this.uriIdentityService.extUri); + const workspaceId = workspaceIdentifier.id; + const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath, uri => this.uriIdentityService.extUri.ignorePathCasing(uri)); + workspace.initialized = this.workspaceConfiguration.initialized; + return workspace; } - private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): Promise { + private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): Workspace { const workspace = new Workspace(singleFolderWorkspaceIdentifier.id, [toWorkspaceFolder(singleFolderWorkspaceIdentifier.uri)], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri)); workspace.initialized = true; - return Promise.resolve(workspace); + return workspace; } private createEmptyWorkspace(emptyWorkspaceIdentifier: IEmptyWorkspaceIdentifier): Promise { @@ -505,9 +540,9 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return result; } - private initializeConfiguration(): Promise { - return this.initializeUserConfiguration() - .then(({ local, remote }) => this.loadConfiguration(local, remote)); + private async initializeConfiguration(): Promise { + const { local, remote } = await this.initializeUserConfiguration(); + await this.loadConfiguration(local, remote); } private initializeUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> { @@ -553,29 +588,29 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return this.onWorkspaceFolderConfigurationChanged(folder); } - private loadConfiguration(userConfigurationModel: ConfigurationModel, remoteUserConfigurationModel: ConfigurationModel): Promise { + private async loadConfiguration(userConfigurationModel: ConfigurationModel, remoteUserConfigurationModel: ConfigurationModel): Promise { // reset caches this.cachedFolderConfigs = new ResourceMap(); const folders = this.workspace.folders; - return this.loadFolderConfigurations(folders) - .then((folderConfigurations) => { + const folderConfigurations = await this.loadFolderConfigurations(folders); - let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations); - const folderConfigurationModels = new ResourceMap(); - folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); + let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations); + const folderConfigurationModels = new ResourceMap(); + folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); - const currentConfiguration = this._configuration; - this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); + const currentConfiguration = this._configuration; + this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); - if (this.initialized) { - const change = this._configuration.compare(currentConfiguration); - this.triggerConfigurationChange(change, { data: currentConfiguration.toData(), workspace: this.workspace }, ConfigurationTarget.WORKSPACE); - } else { - this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, this.workspace, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); - this.initialized = true; - } - }); + if (this.initialized) { + const change = this._configuration.compare(currentConfiguration); + this.triggerConfigurationChange(change, { data: currentConfiguration.toData(), workspace: this.workspace }, ConfigurationTarget.WORKSPACE); + } else { + this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, this.workspace, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE))); + this.initialized = true; + } + + this.updateUnsafeWorkpsaceSettings(); } private getWorkspaceConfigurationModel(folderConfigurations: ConfigurationModel[]): ConfigurationModel { @@ -595,25 +630,26 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat const previousData = this._configuration.toData(); const change = this._configuration.compareAndUpdateDefaultConfiguration(this.defaultConfiguration, keys); if (this.remoteUserConfiguration) { - this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reprocess()); - this._configuration.updateRemoteUserConfiguration(this.remoteUserConfiguration.reprocess()); + this._configuration.updateLocalUserConfiguration(this.localUserConfiguration.reparse()); + this._configuration.updateRemoteUserConfiguration(this.remoteUserConfiguration.reparse()); } if (this.getWorkbenchState() === WorkbenchState.FOLDER) { const folderConfiguration = this.cachedFolderConfigs.get(this.workspace.folders[0].uri); if (folderConfiguration) { - this._configuration.updateWorkspaceConfiguration(folderConfiguration.reprocess()); - this._configuration.updateFolderConfiguration(this.workspace.folders[0].uri, folderConfiguration.reprocess()); + this._configuration.updateWorkspaceConfiguration(folderConfiguration.reparse()); + this._configuration.updateFolderConfiguration(this.workspace.folders[0].uri, folderConfiguration.reparse()); } } else { - this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reprocessWorkspaceSettings()); + this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reparseWorkspaceSettings()); for (const folder of this.workspace.folders) { const folderConfiguration = this.cachedFolderConfigs.get(folder.uri); if (folderConfiguration) { - this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration.reprocess()); + this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration.reparse()); } } } this.triggerConfigurationChange(change, { data: previousData, workspace: this.workspace }, ConfigurationTarget.DEFAULT); + this.updateUnsafeWorkpsaceSettings(); } } @@ -651,6 +687,24 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } + private updateUnsafeWorkpsaceSettings(): string[] { + let unsafeWorkspaceSettings = []; + if (this.getWorkbenchState() === WorkbenchState.WORKSPACE) { + unsafeWorkspaceSettings.push(...this.workspaceConfiguration.getExcludedUnsafeSettings()); + } + for (const folderConfig of this.cachedFolderConfigs.values()) { + unsafeWorkspaceSettings.push(...folderConfig.getExcludedUnsafeSettings()); + } + unsafeWorkspaceSettings = distinct(unsafeWorkspaceSettings).sort((a, b) => a.localeCompare(b)); + const { removed, added } = delta(unsafeWorkspaceSettings, this._unSafeWorkspaceSettings, (a, b) => a.localeCompare(b)); + const changed = [...removed, ...added]; + if (changed.length) { + this._unSafeWorkspaceSettings = unsafeWorkspaceSettings; + this._onDidChangeUnsafeWorkspaceSettings.fire(this.unSafeWorkspaceSettings); + } + return changed; + } + private async updateWorkspaceConfiguration(workspaceFolders: WorkspaceFolder[], configuration: ConfigurationModel): Promise { const previous = { data: this._configuration.toData(), workspace: this.workspace }; const change = this._configuration.compareAndUpdateWorkspaceConfiguration(configuration); @@ -663,20 +717,20 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } else { this.triggerConfigurationChange(change, previous, ConfigurationTarget.WORKSPACE); } + this.updateUnsafeWorkpsaceSettings(); } - private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder): Promise { - return this.loadFolderConfigurations([folder]) - .then(([folderConfiguration]) => { - const previous = { data: this._configuration.toData(), workspace: this.workspace }; - const folderConfiguraitonChange = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration); - if (this.getWorkbenchState() === WorkbenchState.FOLDER) { - const workspaceConfigurationChange = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration); - this.triggerConfigurationChange(mergeChanges(folderConfiguraitonChange, workspaceConfigurationChange), previous, ConfigurationTarget.WORKSPACE); - } else { - this.triggerConfigurationChange(folderConfiguraitonChange, previous, ConfigurationTarget.WORKSPACE_FOLDER); - } - }); + private async onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder): Promise { + const [folderConfiguration] = await this.loadFolderConfigurations([folder]); + const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const folderConfiguraitonChange = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration); + if (this.getWorkbenchState() === WorkbenchState.FOLDER) { + const workspaceConfigurationChange = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration); + this.triggerConfigurationChange(mergeChanges(folderConfiguraitonChange, workspaceConfigurationChange), previous, ConfigurationTarget.WORKSPACE); + } else { + this.triggerConfigurationChange(folderConfiguraitonChange, previous, ConfigurationTarget.WORKSPACE_FOLDER); + } + this.updateUnsafeWorkpsaceSettings(); } private async onFoldersChanged(): Promise { @@ -706,7 +760,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return Promise.all([...folders.map(folder => { let folderConfiguration = this.cachedFolderConfigs.get(folder.uri); if (!folderConfiguration) { - folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.fileService, this.uriIdentityService, this.logService, this.configurationCache); + folderConfiguration = new FolderConfiguration(folder, FOLDER_CONFIG_FOLDER_NAME, this.getWorkbenchState(), this.workspaceTrustState, this.fileService, this.uriIdentityService, this.logService, this.configurationCache); this._register(folderConfiguration.onDidChange(() => this.onWorkspaceFolderConfigurationChanged(folder))); this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration)); } @@ -860,6 +914,17 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } +class ConfigurationWorkspaceTrustContribution extends Disposable implements IWorkbenchContribution { + constructor( + @IWorkspaceTrustService workspaceTrustService: IWorkspaceTrustService, + @IConfigurationService configurationService: WorkspaceService + ) { + super(); + configurationService.updateWorkspaceTrustState(workspaceTrustService.getWorkspaceTrustState()); + this._register(workspaceTrustService.onDidChangeTrustState(e => configurationService.updateWorkspaceTrustState(workspaceTrustService.getWorkspaceTrustState()))); + } +} + class RegisterConfigurationSchemasContribution extends Disposable implements IWorkbenchContribution { constructor( @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @@ -900,4 +965,6 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored); +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(RegisterConfigurationSchemasContribution, LifecyclePhase.Restored); +workbenchContributionsRegistry.registerWorkbenchContribution(ConfigurationWorkspaceTrustContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index f7769371de5..9fc95cfbeea 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -7,6 +7,7 @@ import { ConfigurationScope } from 'vs/platform/configuration/common/configurati import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; export const FOLDER_CONFIG_FOLDER_NAME = '.vscode'; export const FOLDER_SETTINGS_NAME = 'settings'; @@ -47,6 +48,16 @@ export interface IConfigurationCache { export const IWorkbenchConfigurationService = refineServiceDecorator(IConfigurationService); export interface IWorkbenchConfigurationService extends IConfigurationService { + /** + * List of unsafe workspace settings found in current workspace + */ + readonly unSafeWorkspaceSettings: ReadonlyArray; + + /** + * Event that triggers when the list of unsafe workspace settings changes + */ + readonly onDidChangeUnsafeWorkspaceSettings: Event>; + /** * A promise that resolves when the remote configuration is loaded in a remote window. * The promise is resolved immediately if the window is not remote. diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index c2ee0980e88..cc441fccb92 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -5,12 +5,11 @@ import { equals } from 'vs/base/common/objects'; import { toValuesTree, IConfigurationModel, IConfigurationOverrides, IConfigurationValue, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; -import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { Configuration as BaseConfiguration, ConfigurationModelParser, ConfigurationModel, ConfigurationParseOptions } from 'vs/platform/configuration/common/configurationModels'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { Workspace } from 'vs/platform/workspace/common/workspace'; import { ResourceMap } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; -import { WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry'; export class WorkspaceConfigurationModelParser extends ConfigurationModelParser { @@ -22,7 +21,7 @@ export class WorkspaceConfigurationModelParser extends ConfigurationModelParser constructor(name: string) { super(name); - this._settingsModelParser = new ConfigurationModelParser(name, WORKSPACE_SCOPES); + this._settingsModelParser = new ConfigurationModelParser(name); this._launchModel = new ConfigurationModel(); this._tasksModel = new ConfigurationModel(); } @@ -43,16 +42,16 @@ export class WorkspaceConfigurationModelParser extends ConfigurationModelParser return this._tasksModel; } - reprocessWorkspaceSettings(): void { - this._settingsModelParser.parse(); + reparseWorkspaceSettings(configurationParseOptions: ConfigurationParseOptions): void { + this._settingsModelParser.reparse(configurationParseOptions); } - protected override doParseRaw(raw: any): IConfigurationModel { + protected override doParseRaw(raw: any, configurationParseOptions?: ConfigurationParseOptions): IConfigurationModel { this._folders = (raw['folders'] || []) as IStoredWorkspaceFolder[]; - this._settingsModelParser.parseRaw(raw['settings']); + this._settingsModelParser.parseRaw(raw['settings'], configurationParseOptions); this._launchModel = this.createConfigurationModelFrom(raw, 'launch'); this._tasksModel = this.createConfigurationModelFrom(raw, 'tasks'); - return super.doParseRaw(raw); + return super.doParseRaw(raw, configurationParseOptions); } private createConfigurationModelFrom(raw: any, key: string): ConfigurationModel { @@ -74,7 +73,7 @@ export class StandaloneConfigurationModelParser extends ConfigurationModelParser super(name); } - protected override doParseRaw(raw: any): IConfigurationModel { + protected override doParseRaw(raw: any, configurationParseOptions?: ConfigurationParseOptions): IConfigurationModel { const contents = toValuesTree(raw, message => console.error(`Conflict in settings file ${this._name}: ${message}`)); const scopedContents = Object.create(null); scopedContents[this.scope] = contents; diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 74fecfd1931..228cc13113a 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Registry } from 'vs/platform/registry/common/platform'; import { StandaloneConfigurationModelParser, Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; -import { ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationModelParser, ConfigurationModel, ConfigurationParseOptions } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ResourceMap } from 'vs/base/common/map'; import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -49,9 +49,9 @@ suite('FolderSettingsModelParser', () => { }); test('parse all folder settings', () => { - const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]); + const testObject = new ConfigurationModelParser('settings'); - testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' })); + testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' }), { scopes: [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW] }); const expected = Object.create(null); expected['FolderSettingsModelParser'] = Object.create(null); @@ -61,9 +61,9 @@ suite('FolderSettingsModelParser', () => { }); test('parse resource folder settings', () => { - const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE]); + const testObject = new ConfigurationModelParser('settings'); - testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' })); + testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' }), { scopes: [ConfigurationScope.RESOURCE] }); const expected = Object.create(null); expected['FolderSettingsModelParser'] = Object.create(null); @@ -72,9 +72,9 @@ suite('FolderSettingsModelParser', () => { }); test('parse resource and resource language settings', () => { - const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE]); + const testObject = new ConfigurationModelParser('settings'); - testObject.parseContent(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.resourceLanguage': 'resourceLanguage', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' } })); + testObject.parse(JSON.stringify({ '[json]': { 'FolderSettingsModelParser.window': 'window', 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.resourceLanguage': 'resourceLanguage', 'FolderSettingsModelParser.application': 'application', 'FolderSettingsModelParser.machine': 'executable' } }), { scopes: [ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE] }); const expected = Object.create(null); expected['FolderSettingsModelParser'] = Object.create(null); @@ -83,10 +83,11 @@ suite('FolderSettingsModelParser', () => { assert.deepStrictEqual(testObject.configurationModel.overrides, [{ 'contents': expected, 'identifiers': ['json'], 'keys': ['FolderSettingsModelParser.resource', 'FolderSettingsModelParser.resourceLanguage'] }]); }); - test('reprocess folder settings excludes application and machine setting', () => { - const testObject = new ConfigurationModelParser('settings', [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW]); + test('reparse folder settings excludes application and machine setting', () => { + const parseOptions: ConfigurationParseOptions = { scopes: [ConfigurationScope.RESOURCE, ConfigurationScope.WINDOW] }; + const testObject = new ConfigurationModelParser('settings'); - testObject.parseContent(JSON.stringify({ 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.anotherApplicationSetting': 'executable' })); + testObject.parse(JSON.stringify({ 'FolderSettingsModelParser.resource': 'resource', 'FolderSettingsModelParser.anotherApplicationSetting': 'executable' }), parseOptions); let expected = Object.create(null); expected['FolderSettingsModelParser'] = Object.create(null); @@ -112,7 +113,7 @@ suite('FolderSettingsModelParser', () => { } }); - testObject.parse(); + testObject.reparse(parseOptions); expected = Object.create(null); expected['FolderSettingsModelParser'] = Object.create(null); @@ -127,7 +128,7 @@ suite('StandaloneConfigurationModelParser', () => { test('parse tasks stand alone configuration model', () => { const testObject = new StandaloneConfigurationModelParser('tasks', 'tasks'); - testObject.parseContent(JSON.stringify({ 'version': '1.1.1', 'tasks': [] })); + testObject.parse(JSON.stringify({ 'version': '1.1.1', 'tasks': [] })); const expected = Object.create(null); expected['tasks'] = Object.create(null); @@ -204,6 +205,6 @@ suite('Workspace Configuration', () => { function toConfigurationModel(obj: any): ConfigurationModel { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify(obj)); + parser.parse(JSON.stringify(obj)); return parser.configurationModel; } diff --git a/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts index 08a15f45ed2..73453f39275 100644 --- a/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts @@ -732,7 +732,7 @@ suite('ExtHostConfiguration', function () { function toConfigurationModel(obj: any): ConfigurationModel { const parser = new ConfigurationModelParser('test'); - parser.parseContent(JSON.stringify(obj)); + parser.parse(JSON.stringify(obj)); return parser.configurationModel; }