From e4c04ea2e99dc6afc6f2c645ec4541ec8df9ed09 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 10 May 2022 13:49:31 +0200 Subject: [PATCH 01/34] - Introduce policy model with file policy - Introduce policy configuration using policy model - Use policy configuration while reading, inspecting and writing configuration - Adopt json settings editor --- .../sharedProcess/sharedProcessMain.ts | 2 +- src/vs/code/electron-main/main.ts | 2 +- src/vs/code/node/cliProcessMain.ts | 2 +- .../standalone/browser/standaloneServices.ts | 6 +- .../configuration/common/configuration.ts | 3 + .../common/configurationModels.ts | 82 +++++----- .../common/configurationService.ts | 36 +++-- .../configuration/common/configurations.ts | 143 ++++++++++++++++++ .../test/common/configurationModels.test.ts | 33 ++-- .../test/common/configurationService.test.ts | 26 ++-- src/vs/platform/environment/common/argv.ts | 1 + .../environment/common/environment.ts | 3 + .../environment/common/environmentService.ts | 3 + src/vs/platform/environment/node/argv.ts | 1 + src/vs/platform/policy/common/policy.ts | 112 ++++++++++++++ .../test/common/userDataSyncClient.ts | 2 +- .../node/remoteExtensionHostAgentCli.ts | 2 +- src/vs/server/node/serverServices.ts | 2 +- .../api/common/extHostConfiguration.ts | 2 +- .../test/browser/extHostConfiguration.test.ts | 5 + .../browser/preferencesRenderers.ts | 26 ++++ .../configuration/browser/configuration.ts | 32 ++-- .../browser/configurationService.ts | 32 +++- .../common/configurationEditingService.ts | 10 ++ .../common/configurationModels.ts | 3 +- .../test/common/configurationModels.test.ts | 8 +- 26 files changed, 458 insertions(+), 121 deletions(-) create mode 100644 src/vs/platform/configuration/common/configurations.ts create mode 100644 src/vs/platform/policy/common/policy.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 4dd6d42da9b..aa4c7ad67e7 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -222,7 +222,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); services.set(IConfigurationService, configurationService); // Storage (global access only) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 762f288daba..7b95aaad618 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -167,7 +167,7 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Configuration - const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService); + const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, environmentMainService, logService); services.set(IConfigurationService, configurationService); // Lifecycle diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 9495713716e..6ae96fc2047 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -129,7 +129,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); services.set(IConfigurationService, configurationService); // Init config diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index ef4d4407aa9..52803c25a6a 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -29,7 +29,7 @@ import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfiguration'; import { CommandsRegistry, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { Configuration, ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { Configuration, ConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IInputResult, IShowResult } from 'vs/platform/dialogs/common/dialogs'; import { createDecorator, IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; @@ -88,6 +88,7 @@ import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/com import { staticObservableValue } from 'vs/base/common/observableValue'; import 'vs/editor/common/services/languageFeaturesService'; +import { DefaultConfigurationModel } from 'vs/platform/configuration/common/configurations'; class SimpleModel implements IResolvedTextEditorModel { @@ -521,7 +522,7 @@ export class StandaloneConfigurationService implements IConfigurationService { private readonly _configuration: Configuration; constructor() { - this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); + this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); } getValue(): T; @@ -582,6 +583,7 @@ export class StandaloneConfigurationService implements IConfigurationService { }; return { defaults: emptyModel, + policy: emptyModel, user: emptyModel, workspace: emptyModel, folders: [] diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 6ed2b0c3ef8..4012d405858 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -80,6 +80,7 @@ export interface IConfigurationValue { readonly workspaceValue?: T; readonly workspaceFolderValue?: T; readonly memoryValue?: T; + readonly policyValue?: T; readonly value?: T; readonly default?: { value?: T; override?: T }; @@ -89,6 +90,7 @@ export interface IConfigurationValue { readonly workspace?: { value?: T; override?: T }; readonly workspaceFolder?: { value?: T; override?: T }; readonly memory?: { value?: T; override?: T }; + readonly policy?: { value?: T }; readonly overrideIdentifiers?: string[]; } @@ -163,6 +165,7 @@ export interface IOverrides { export interface IConfigurationData { defaults: IConfigurationModel; + policy: IConfigurationModel; user: IConfigurationModel; workspace: IConfigurationModel; folders: [UriComponents, IConfigurationModel][]; diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 8afa3b7f065..03135dbdbb4 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -232,33 +232,6 @@ export class ConfigurationModel implements IConfigurationModel { } } -export class DefaultConfigurationModel extends ConfigurationModel { - - constructor(configurationDefaultsOverrides: IStringDictionary = {}) { - const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - const keys = Object.keys(properties); - const contents: any = Object.create(null); - const overrides: IOverrides[] = []; - - for (const key in properties) { - const defaultOverrideValue = configurationDefaultsOverrides[key]; - const value = defaultOverrideValue !== undefined ? defaultOverrideValue : properties[key].default; - addToValueTree(contents, key, value, message => console.error(`Conflict in default settings: ${message}`)); - } - for (const key of Object.keys(contents)) { - if (OVERRIDE_PROPERTY_REGEX.test(key)) { - overrides.push({ - identifiers: overrideIdentifiersFromKey(key), - keys: Object.keys(contents[key]), - contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)), - }); - } - } - - super(contents, keys, overrides); - } -} - export interface ConfigurationParseOptions { scopes: ConfigurationScope[] | undefined; skipRestricted?: boolean; @@ -473,6 +446,7 @@ export class Configuration { constructor( private _defaultConfiguration: ConfigurationModel, + private _policyConfiguration: ConfigurationModel, private _localUserConfiguration: ConfigurationModel, private _remoteUserConfiguration: ConfigurationModel = new ConfigurationModel(), private _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), @@ -483,7 +457,7 @@ export class Configuration { } getValue(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | undefined): any { - const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace); + const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(section, overrides, workspace); return consolidateConfigurationModel.getValue(section); } @@ -511,11 +485,12 @@ export class Configuration { } inspect(key: string, overrides: IConfigurationOverrides, workspace: Workspace | undefined): IConfigurationValue { - const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace); + const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(key, overrides, workspace); const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource, workspace); const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration; const defaultValue = overrides.overrideIdentifier ? this._defaultConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._defaultConfiguration.freeze().getValue(key); + const policyValue = this._policyConfiguration.freeze().getValue(key); const userValue = overrides.overrideIdentifier ? this.userConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.userConfiguration.freeze().getValue(key); const userLocalValue = overrides.overrideIdentifier ? this.localUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.localUserConfiguration.freeze().getValue(key); const userRemoteValue = overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.remoteUserConfiguration.freeze().getValue(key); @@ -523,19 +498,21 @@ export class Configuration { const workspaceFolderValue = folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : undefined; const memoryValue = overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.getValue(key); const value = consolidateConfigurationModel.getValue(key); - const overrideIdentifiers: string[] = arrays.distinct(arrays.flatten(consolidateConfigurationModel.overrides.map(override => override.identifiers))).filter(overrideIdentifier => consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined); + const overrideIdentifiers: string[] = arrays.distinct(consolidateConfigurationModel.overrides.map(override => override.identifiers).flat()).filter(overrideIdentifier => consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined); return { - defaultValue: defaultValue, - userValue: userValue, - userLocalValue: userLocalValue, - userRemoteValue: userRemoteValue, - workspaceValue: workspaceValue, - workspaceFolderValue: workspaceFolderValue, - memoryValue: memoryValue, + defaultValue, + policyValue, + userValue, + userLocalValue, + userRemoteValue, + workspaceValue, + workspaceFolderValue, + memoryValue, value, default: defaultValue !== undefined ? { value: this._defaultConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + policy: policyValue !== undefined ? { value: policyValue } : undefined, user: userValue !== undefined ? { value: this.userConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.userConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, userLocal: userLocalValue !== undefined ? { value: this.localUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.localUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, userRemote: userRemoteValue !== undefined ? { value: this.remoteUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, @@ -568,6 +545,10 @@ export class Configuration { this._foldersConsolidatedConfigurations.clear(); } + updatePolicyConfiguration(policyConfiguration: ConfigurationModel): void { + this._policyConfiguration = policyConfiguration; + } + updateLocalUserConfiguration(localUserConfiguration: ConfigurationModel): void { this._localUserConfiguration = localUserConfiguration; this._userConfiguration = null; @@ -620,6 +601,15 @@ export class Configuration { return { keys, overrides }; } + compareAndUpdatePolicyConfiguration(policyConfiguration: ConfigurationModel): IConfigurationChange { + const { added, updated, removed } = compare(this._policyConfiguration, policyConfiguration); + const keys = [...added, ...updated, ...removed]; + if (keys.length) { + this.updatePolicyConfiguration(policyConfiguration); + } + return { keys, overrides: [] }; + } + compareAndUpdateLocalUserConfiguration(user: ConfigurationModel): IConfigurationChange { const { added, updated, removed, overrides } = compare(this.localUserConfiguration, user); const keys = [...added, ...updated, ...removed]; @@ -698,9 +688,15 @@ export class Configuration { return this._folderConfigurations; } - private getConsolidateConfigurationModel(overrides: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { + private getConsolidatedConfigurationModel(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides, workspace); - return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; + if (overrides.overrideIdentifier) { + configurationModel = configurationModel.override(overrides.overrideIdentifier); + } + if (!this._policyConfiguration.isEmpty() && this._policyConfiguration.getValue(section) !== undefined) { + configurationModel = configurationModel.merge(this._policyConfiguration); + } + return configurationModel; } private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { @@ -765,6 +761,11 @@ export class Configuration { overrides: this._defaultConfiguration.overrides, keys: this._defaultConfiguration.keys }, + policy: { + contents: this._policyConfiguration.contents, + overrides: this._policyConfiguration.overrides, + keys: this._policyConfiguration.keys + }, user: { contents: this.userConfiguration.contents, overrides: this.userConfiguration.overrides, @@ -812,13 +813,14 @@ export class Configuration { static parse(data: IConfigurationData): Configuration { const defaultConfiguration = this.parseConfigurationModel(data.defaults); + const policyConfiguration = this.parseConfigurationModel(data.policy); const userConfiguration = this.parseConfigurationModel(data.user); const workspaceConfiguration = this.parseConfigurationModel(data.workspace); const folders: ResourceMap = data.folders.reduce((result, value) => { result.set(URI.revive(value[0]), this.parseConfigurationModel(value[1])); return result; }, new ResourceMap()); - return new Configuration(defaultConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false); + return new Configuration(defaultConfiguration, policyConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false); } private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel { diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 7860c72ea8e..b3ff50a6d0c 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -9,17 +9,20 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; -import { Configuration, ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Configuration, ConfigurationChangeEvent, ConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; +import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { ILogService } from 'vs/platform/log/common/log'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { declare readonly _serviceBrand: undefined; private configuration: Configuration; - private userConfiguration: UserSettings; + private readonly defaultConfiguration: DefaultConfiguration; + private readonly policyConfiguration: PolicyConfiguration; + private readonly userConfiguration: UserSettings; private readonly reloadConfigurationScheduler: RunOnceScheduler; private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); @@ -27,20 +30,25 @@ export class ConfigurationService extends Disposable implements IConfigurationSe constructor( private readonly settingsResource: URI, - fileService: IFileService + fileService: IFileService, + environmentService: IEnvironmentService, + logService: ILogService, ) { super(); + this.defaultConfiguration = this._register(new DefaultConfiguration()); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, fileService, environmentService, logService)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); - this.configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); + this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel()); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50)); - this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(({ properties }) => this.onDidDefaultConfigurationChange(properties))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ defaults, properties }) => this.onDidDefaultConfigurationChange(defaults, properties))); + this._register(this.policyConfiguration.onDidChangeConfiguration(model => this.onDidPolicyConfigurationChange(model))); this._register(this.userConfiguration.onDidChange(() => this.reloadConfigurationScheduler.schedule())); } async initialize(): Promise { - const userConfiguration = await this.userConfiguration.loadConfiguration(); - this.configuration = new Configuration(new DefaultConfigurationModel(), userConfiguration); + const [defaultModel, policyModel, userModel] = await Promise.all([this.defaultConfiguration.initialize(), this.policyConfiguration.initialize(), this.userConfiguration.loadConfiguration()]); + this.configuration = new Configuration(defaultModel, policyModel, userModel); } getConfigurationData(): IConfigurationData { @@ -89,9 +97,15 @@ export class ConfigurationService extends Disposable implements IConfigurationSe this.trigger(change, previous, ConfigurationTarget.USER); } - private onDidDefaultConfigurationChange(properties: string[]): void { + private onDidDefaultConfigurationChange(defaultConfigurationModel: ConfigurationModel, properties: string[]): void { const previous = this.configuration.toData(); - const change = this.configuration.compareAndUpdateDefaultConfiguration(new DefaultConfigurationModel(), properties); + const change = this.configuration.compareAndUpdateDefaultConfiguration(defaultConfigurationModel, properties); + this.trigger(change, previous, ConfigurationTarget.DEFAULT); + } + + private onDidPolicyConfigurationChange(policyConfiguration: ConfigurationModel): void { + const previous = this.configuration.toData(); + const change = this.configuration.compareAndUpdatePolicyConfiguration(policyConfiguration); this.trigger(change, previous, ConfigurationTarget.DEFAULT); } diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts new file mode 100644 index 00000000000..a86a1f50e49 --- /dev/null +++ b/src/vs/platform/configuration/common/configurations.ts @@ -0,0 +1,143 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IStringDictionary } from 'vs/base/common/collections'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { addToValueTree, IOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { PolicyModel } from 'vs/platform/policy/common/policy'; +import { Registry } from 'vs/platform/registry/common/platform'; + +export class DefaultConfiguration extends Disposable { + + private readonly _onDidChangeConfiguration = this._register(new Emitter<{ defaults: ConfigurationModel; properties: string[] }>()); + readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; + + private _configurationModel: ConfigurationModel | undefined; + get configurationModel(): ConfigurationModel { + if (!this._configurationModel) { + this._configurationModel = new DefaultConfigurationModel(this.getConfigurationDefaultOverrides()); + } + return this._configurationModel; + } + + async initialize(): Promise { + this._configurationModel = undefined; + this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(({ properties, defaultsOverrides }) => this.onDidUpdateConfiguration(properties, defaultsOverrides))); + return this.configurationModel; + } + + reload(): ConfigurationModel { + this._configurationModel = undefined; + return this.configurationModel; + } + + protected onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void { + this._configurationModel = undefined; + this._onDidChangeConfiguration.fire({ defaults: this.configurationModel, properties }); + } + + protected getConfigurationDefaultOverrides(): IStringDictionary { + return {}; + } + +} + +export class DefaultConfigurationModel extends ConfigurationModel { + + constructor(configurationDefaultsOverrides: IStringDictionary = {}) { + const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const keys = Object.keys(properties); + const contents: any = Object.create(null); + const overrides: IOverrides[] = []; + + for (const key in properties) { + const defaultOverrideValue = configurationDefaultsOverrides[key]; + const value = defaultOverrideValue !== undefined ? defaultOverrideValue : properties[key].default; + addToValueTree(contents, key, value, message => console.error(`Conflict in default settings: ${message}`)); + } + for (const key of Object.keys(contents)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { + overrides.push({ + identifiers: overrideIdentifiersFromKey(key), + keys: Object.keys(contents[key]), + contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)), + }); + } + } + + super(contents, keys, overrides); + } +} + +export class PolicyConfiguration extends Disposable { + + private readonly _onDidChangeConfiguration = this._register(new Emitter()); + readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; + + private readonly policies: PolicyModel; + + constructor( + private readonly defaultConfiguration: DefaultConfiguration, + fileService: IFileService, + environmentService: IEnvironmentService, + logService: ILogService + ) { + super(); + this.policies = new PolicyModel(fileService, environmentService, logService); + } + + private _configurationModel: ConfigurationModel | undefined; + get configurationModel(): ConfigurationModel { + if (!this._configurationModel) { + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const keys: string[] = []; + const contents: any = Object.create(null); + for (const key of this.defaultConfiguration.configurationModel.keys) { + const policyName = configurationProperties[key].policy?.name; + if (!policyName) { + continue; + } + const value = this.policies.getPolicy(policyName); + if (value === undefined) { + continue; + } + keys.push(key); + addToValueTree(contents, key, value, message => console.error(`Conflict in policy settings: ${message}`)); + } + this._configurationModel = new ConfigurationModel(contents, keys, []); + } + return this._configurationModel; + } + + async initialize(): Promise { + await this.policies.initialize(); + this._register(this.policies.onDidChange(e => this.onDidChange())); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.onDidDefaultConfigurationChange(properties))); + return this.reload(); + } + + reload(): ConfigurationModel { + this._configurationModel = undefined; + return this.configurationModel; + } + + private onDidDefaultConfigurationChange(properties: string[]): void { + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + if (properties.some(key => configurationProperties[key].policy?.name)) { + this.onDidChange(); + } + } + + private onDidChange(): void { + this._onDidChangeConfiguration.fire(this.reload()); + } + +} diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index ae1add24a78..9887f80e184 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -6,8 +6,9 @@ import * as assert from 'assert'; import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { AllKeysConfigurationChangeEvent, Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser, DefaultConfigurationModel, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; +import { AllKeysConfigurationChangeEvent, Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { DefaultConfigurationModel } from 'vs/platform/configuration/common/configurations'; import { Registry } from 'vs/platform/registry/common/platform'; import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; @@ -496,7 +497,7 @@ suite('Configuration', () => { test('Test update value', () => { const parser = new ConfigurationModelParser('test'); parser.parse(JSON.stringify({ 'a': 1 })); - const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); + const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel()); testObject.updateValue('a', 2); @@ -506,7 +507,7 @@ suite('Configuration', () => { test('Test update value after inspect', () => { const parser = new ConfigurationModelParser('test'); parser.parse(JSON.stringify({ 'a': 1 })); - const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); + const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel()); testObject.inspect('a', {}, undefined); testObject.updateValue('a', 2); @@ -515,7 +516,7 @@ suite('Configuration', () => { }); test('Test compare and update default configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateDefaultConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on', })); @@ -532,7 +533,7 @@ suite('Configuration', () => { }); test('Test compare and update user configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateLocalUserConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -555,7 +556,7 @@ suite('Configuration', () => { }); test('Test compare and update workspace configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -578,7 +579,7 @@ suite('Configuration', () => { }); test('Test compare and update workspace folder configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -601,7 +602,7 @@ suite('Configuration', () => { }); test('Test compare and delete workspace folder configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -627,7 +628,7 @@ suite('Configuration', () => { suite('ConfigurationChangeEvent', () => { test('changeEvent affecting keys with new configuration', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'window.zoomLevel': 1, 'workbench.editor.enablePreview': false, @@ -653,7 +654,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting keys when configuration changed', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateLocalUserConfiguration(toConfigurationModel({ 'window.zoomLevel': 2, 'workbench.editor.enablePreview': true, @@ -682,7 +683,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting overrides with new configuration', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'files.autoSave': 'off', '[markdown]': { @@ -724,7 +725,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting overrides when configuration changed', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateLocalUserConfiguration(toConfigurationModel({ 'workbench.editor.enablePreview': true, '[markdown]': { @@ -791,7 +792,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting workspace folders', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'custom' })); configuration.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true })); configuration.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'workbench.editor.enablePreview': true, 'window.restoreWindows': true })); @@ -881,7 +882,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent - all', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true })); const data = configuration.toData(); const change = mergeChanges( @@ -976,7 +977,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting tasks and launches', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'launch': { 'configuraiton': {} @@ -999,7 +1000,7 @@ suite('ConfigurationChangeEvent', () => { suite('AllKeysConfigurationChangeEvent', () => { test('changeEvent', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateDefaultConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', '[markdown]': { diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index 559803b0588..04635ed013c 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -12,9 +12,11 @@ import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { NullLogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -22,6 +24,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; suite('ConfigurationService', () => { let fileService: IFileService; + let environmentService: IEnvironmentService; let settingsResource: URI; const disposables: DisposableStore = new DisposableStore(); @@ -30,13 +33,14 @@ suite('ConfigurationService', () => { const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); settingsResource = URI.file('settings.json'); + environmentService = new TestInstantiationService().mock(IEnvironmentService) as IEnvironmentService; }); teardown(() => disposables.clear()); test('simple', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -49,7 +53,7 @@ suite('ConfigurationService', () => { test('config gets flattened', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ testworkbench: { @@ -68,7 +72,7 @@ suite('ConfigurationService', () => { test('error case does not explode', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString(',,,,')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -78,7 +82,7 @@ suite('ConfigurationService', () => { }); test('missing file does not explode', async () => { - const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService)); + const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string }>(); @@ -87,7 +91,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file does not exist', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); return new Promise((c, e) => { disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => { @@ -100,7 +104,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file exists', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); await testObject.initialize(); @@ -116,7 +120,7 @@ suite('ConfigurationService', () => { test('reloadConfiguration', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); let config = testObject.getValue<{ foo: string; @@ -155,7 +159,7 @@ suite('ConfigurationService', () => { } }); - let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService)); + let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, environmentService, new NullLogService())); await testObject.initialize(); let setting = testObject.getValue(); @@ -163,7 +167,7 @@ suite('ConfigurationService', () => { assert.strictEqual(setting.configuration.service.testSetting, 'isSet'); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); setting = testObject.getValue(); @@ -191,7 +195,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); testObject.initialize(); let res = testObject.inspect('something.missing'); @@ -226,7 +230,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); testObject.initialize(); let res = testObject.inspect('lookup.service.testNullSetting'); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index b2b4500a614..3834247b45b 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -88,6 +88,7 @@ export interface NativeParsedArgs { 'sync'?: 'on' | 'off'; '__sandbox'?: boolean; 'logsPath'?: string; + 'policy-file'?: string; // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 56531a3a784..e0feccde448 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -87,6 +87,9 @@ export interface IEnvironmentService { telemetryLogResource: URI; serviceMachineIdResource: URI; + // --- Policy + policyFile?: URI; + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index fcea3f58023..d3214f7036f 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -237,6 +237,9 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron @memoize get disableWorkspaceTrust(): boolean { return !!this.args['disable-workspace-trust']; } + @memoize + get policyFile(): URI | undefined { return this.args['policy-file'] ? URI.file(this.args['policy-file']) : undefined; } + get args(): NativeParsedArgs { return this._args; } constructor( diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 062ce48a580..0a69e9cd2c4 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -73,6 +73,7 @@ export const OPTIONS: OptionDescriptions> = { 'disable-extensions': { type: 'boolean', deprecates: ['disableExtensions'], cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, 'disable-extension': { type: 'string[]', cat: 't', args: 'ext-id', description: localize('disableExtension', "Disable an extension.") }, 'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off."), args: ['on | off'] }, + 'policy-file': { type: 'string', cat: 't', description: localize('policyFile', "Path of the file defining policies") }, 'inspect-extensions': { type: 'string', deprecates: ['debugPluginHost'], args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, 'inspect-brk-extensions': { type: 'string', deprecates: ['debugBrkPluginHost'], args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts new file mode 100644 index 00000000000..5f9b1cfafe9 --- /dev/null +++ b/src/vs/platform/policy/common/policy.ts @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ThrottledDelayer } from 'vs/base/common/async'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; + +export type PolicyValue = string | boolean | number; +export type Policies = IStringDictionary; + +export class PolicyModel extends Disposable { + readonly _serviceBrand: undefined; + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + private policies: Policies = {}; + + constructor( + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @ILogService private readonly logService: ILogService, + ) { + super(); + } + + async initialize(): Promise { + const policy = this.environmentService.policyFile ? new FilePolicy(this.environmentService.policyFile, this.fileService, this.logService) : new NullPolicy(); + this.policies = await policy.read(); + this._register(policy.onDidChange(({ changed, policies }) => { + this.policies = policies; + this._onDidChange.fire(changed); + })); + } + + getPolicy(name: string): PolicyValue | undefined { + return this.policies[name]; + } +} + +export interface IPolicy { + readonly onDidChange: Event<{ changed: string[]; policies: Policies }>; + read(): Promise; +} + +class FilePolicy extends Disposable implements IPolicy { + + private policies: Policies = {}; + + private readonly _onDidChange = new Emitter<{ changed: string[]; policies: Policies }>(); + readonly onDidChange = this._onDidChange.event; + + private readonly throttledDelayer = this._register(new ThrottledDelayer(500)); + + constructor( + private readonly file: URI, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService + ) { + super(); + this._register(fileService.watch(file)); + this._register(Event.filter(fileService.onDidFilesChange, e => e.affects(file))(e => this.throttledDelayer.trigger(() => this.onDidFileChange()))); + } + + async read(): Promise { + try { + const content = await this.fileService.readFile(this.file); + this.policies = JSON.parse(content.value.toString()); + } catch (error) { + this.logService.error(error); + this.policies = {}; + } + return this.policies; + } + + private async onDidFileChange(): Promise { + const old = this.policies; + await this.read(); + const changed = this.compare(old, this.policies); + if (changed.length > 0) { + this._onDidChange.fire({ changed, policies: this.policies }); + } + } + + private compare(old: Policies, current: Policies): string[] { + const changed: string[] = []; + for (const key of Object.keys(old)) { + if (old[key] !== current[key]) { + changed.push(key); + } + } + for (const key of Object.keys(current)) { + if (old[key] === undefined) { + changed.push(key); + } + } + return changed; + } + +} + +class NullPolicy extends Disposable implements IPolicy { + readonly onDidChange = Event.None; + async read(): Promise { return {}; } +} diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 293324b83c5..5d3ba1f21bd 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -86,7 +86,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IStorageService, this._register(new InMemoryStorageService())); - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); this.instantiationService.stub(IUriIdentityService, this.instantiationService.createInstance(UriIdentityService)); diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index f1e8986f5c5..170fbafa06a 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -91,7 +91,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, this._register(new DiskFileSystemProvider(logService))); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); await configurationService.initialize(); services.set(IConfigurationService, configurationService); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index a072fa8bb0e..a443d3bc84c 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -107,7 +107,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IFileService, fileService); fileService.registerProvider(Schemas.file, disposables.add(new DiskFileSystemProvider(logService))); - const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService); + const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, environmentService, logService); services.set(IConfigurationService, configurationService); const extensionHostStatusService = new ExtensionHostStatusService(); diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 3a45d62f28f..0bd5f8e9752 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -256,7 +256,7 @@ export class ExtHostConfigProvider { return { key, - defaultValue: config.default?.value, + defaultValue: config.policy?.value ?? config.default?.value, globalValue: config.user?.value, workspaceValue: config.workspace?.value, workspaceFolderValue: config.workspaceFolder?.value, diff --git a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts index 764c1c294b1..950c278a1ff 100644 --- a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts +++ b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts @@ -43,6 +43,7 @@ suite('ExtHostConfiguration', function () { function createConfigurationData(contents: any): IConfigurationInitData { return { defaults: new ConfigurationModel(contents), + policy: new ConfigurationModel(), user: new ConfigurationModel(contents), workspace: new ConfigurationModel(), folders: [], @@ -279,6 +280,7 @@ suite('ExtHostConfiguration', function () { 'wordWrap': 'off' } }, ['editor.wordWrap']), + policy: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -328,6 +330,7 @@ suite('ExtHostConfiguration', function () { 'wordWrap': 'off' } }, ['editor.wordWrap']), + policy: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -405,6 +408,7 @@ suite('ExtHostConfiguration', function () { 'lineNumbers': 'on' } }, ['editor.wordWrap']), + policy: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -508,6 +512,7 @@ suite('ExtHostConfiguration', function () { 'editor.wordWrap': 'bounded', } }), + policy: new ConfigurationModel(), user: toConfigurationModel({ 'editor.wordWrap': 'bounded', '[typescript]': { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 606a9255884..4d2b985ec74 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -175,6 +175,7 @@ class EditSettingRenderer extends Disposable { constructor(private editor: ICodeEditor, private primarySettingsModel: ISettingsEditorModel, private settingHighlighter: SettingHighlighter, + @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { @@ -283,6 +284,9 @@ class EditSettingRenderer extends Disposable { return this.getSettingsAtLineNumber(lineNumber).filter(setting => { const configurationNode = configurationMap[setting.key]; if (configurationNode) { + if (configurationNode.policy && this.configurationService.inspect(setting.key).policyValue !== undefined) { + return false; + } if (this.isDefaultSettings()) { if (setting.key === 'launch') { // Do not show because of https://github.com/microsoft/vscode/issues/32593 @@ -522,6 +526,9 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc for (const setting of section.settings) { const configuration = configurationRegistry[setting.key]; if (configuration) { + if (this.handlePolicyConfiguration(setting, configuration, markerData)) { + continue; + } switch (this.settingsEditorModel.configurationTarget) { case ConfigurationTarget.USER_LOCAL: this.handleLocalUserConfiguration(setting, configuration, markerData); @@ -550,6 +557,25 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc return markerData; } + private handlePolicyConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): boolean { + if (!configuration.policy) { + return false; + } + if (this.configurationService.inspect(setting.key).policyValue === undefined) { + return false; + } + if (this.settingsEditorModel.configurationTarget === ConfigurationTarget.DEFAULT) { + return false; + } + markerData.push({ + severity: MarkerSeverity.Hint, + tags: [MarkerTag.Unnecessary], + ...setting.range, + message: nls.localize('unsupportedPolicySetting', "This setting cannot be applied because it is configured in the system policy.") + }); + return true; + } + private handleLocalUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void { if (this.environmentService.remoteAuthority && (configuration.scope === ConfigurationScope.MACHINE || configuration.scope === ConfigurationScope.MACHINE_OVERRIDABLE)) { markerData.push({ diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 2bd9b092c9a..39ed9b5e64c 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, FileOperation, FileOperationEvent } from 'vs/platform/files/common/files'; -import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, DefaultConfigurationModel, 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 } from 'vs/platform/workspaces/common/workspaces'; @@ -26,8 +26,9 @@ import { joinPath } from 'vs/base/common/resources'; import { Registry } from 'vs/platform/registry/common/platform'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { isObject } from 'vs/base/common/types'; +import { DefaultConfiguration as BaseDefaultConfiguration } from 'vs/platform/configuration/common/configurations'; -export class DefaultConfiguration extends Disposable { +export class DefaultConfiguration extends BaseDefaultConfiguration { static readonly DEFAULT_OVERRIDES_CACHE_EXISTS_KEY = 'DefaultOverridesCacheExists'; @@ -35,9 +36,6 @@ export class DefaultConfiguration extends Disposable { private cachedConfigurationDefaultsOverrides: IStringDictionary = {}; private readonly cacheKey: ConfigurationKey = { type: 'defaults', key: 'configurationDefaultsOverrides' }; - private readonly _onDidChangeConfiguration = this._register(new Emitter<{ defaults: ConfigurationModel; properties: string[] }>()); - readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; - private updateCache: boolean = false; constructor( @@ -50,27 +48,20 @@ export class DefaultConfiguration extends Disposable { } } - private _configurationModel: ConfigurationModel | undefined; - get configurationModel(): ConfigurationModel { - if (!this._configurationModel) { - this._configurationModel = new DefaultConfigurationModel(this.cachedConfigurationDefaultsOverrides); - } - return this._configurationModel; + protected override getConfigurationDefaultOverrides(): IStringDictionary { + return this.cachedConfigurationDefaultsOverrides; } - async initialize(): Promise { + override async initialize(): Promise { await this.initializeCachedConfigurationDefaultsOverrides(); - this._configurationModel = undefined; - this._register(this.configurationRegistry.onDidUpdateConfiguration(({ properties, defaultsOverrides }) => this.onDidUpdateConfiguration(properties, defaultsOverrides))); - return this.configurationModel; + return super.initialize(); } - reload(): ConfigurationModel { + override reload(): ConfigurationModel { this.updateCache = true; this.cachedConfigurationDefaultsOverrides = {}; - this._configurationModel = undefined; this.updateCachedConfigurationDefaultsOverrides(); - return this.configurationModel; + return super.reload(); } private initiaizeCachedConfigurationDefaultsOverridesPromise: Promise | undefined; @@ -92,9 +83,8 @@ export class DefaultConfiguration extends Disposable { return this.initiaizeCachedConfigurationDefaultsOverridesPromise; } - private onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void { - this._configurationModel = undefined; - this._onDidChangeConfiguration.fire({ defaults: this.configurationModel, properties }); + protected override onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void { + super.onDidUpdateConfiguration(properties, defaultsOverrides); if (defaultsOverrides) { this.updateCachedConfigurationDefaultsOverrides(); } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 45af17cc175..a4c11121290 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -13,6 +13,7 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder, IWorkspaceFoldersWillChangeEvent, IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier, IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -54,7 +55,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private readonly configurationCache: IConfigurationCache; private _configuration: Configuration; private initialized: boolean = false; - private defaultConfiguration: DefaultConfiguration; + private readonly defaultConfiguration: DefaultConfiguration; + private readonly policyConfiguration: PolicyConfiguration; private localUserConfiguration: UserConfiguration; private remoteUserConfiguration: RemoteUserConfiguration | null = null; private workspaceConfiguration: WorkspaceConfiguration; @@ -109,12 +111,13 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.initRemoteUserConfigurationBarrier = new Barrier(); this.completeWorkspaceBarrier = new Barrier(); - this.defaultConfiguration = new DefaultConfiguration(configurationCache, environmentService); + this.defaultConfiguration = this._register(new DefaultConfiguration(configurationCache, environmentService)); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, fileService, environmentService, logService)); this.configurationCache = configurationCache; this.fileService = fileService; this.uriIdentityService = uriIdentityService; this.logService = logService; - this._configuration = new Configuration(this.defaultConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); + this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); this.cachedFolderConfigs = new ResourceMap(); this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, fileService, uriIdentityService, logService)); this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); @@ -138,6 +141,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat })); this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties, defaults }) => this.onDefaultConfigurationChanged(defaults, properties))); + this._register(this.policyConfiguration.onDidChangeConfiguration(configurationModel => this.onPolicyConfigurationChanged(configurationModel))); this.workspaceEditingQueue = new Queue(); } @@ -572,12 +576,18 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private async initializeConfiguration(): Promise { await this.defaultConfiguration.initialize(); - mark('code/willInitUserConfiguration'); - const { local, remote } = await this.initializeUserConfiguration(); - mark('code/didInitUserConfiguration'); + const [, user] = await Promise.all([ + this.policyConfiguration.initialize(), + (async () => { + mark('code/willInitUserConfiguration'); + const result = await this.initializeUserConfiguration(); + mark('code/didInitUserConfiguration'); + return result; + })() + ]); mark('code/willInitWorkspaceConfiguration'); - await this.loadConfiguration(local, remote); + await this.loadConfiguration(user.local, user.remote); mark('code/didInitWorkspaceConfiguration'); } @@ -640,7 +650,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); const currentConfiguration = this._configuration; - this._configuration = new Configuration(this.defaultConfiguration.configurationModel, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); + this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); if (this.initialized) { const change = this._configuration.compare(currentConfiguration); @@ -692,6 +702,12 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } + private onPolicyConfigurationChanged(policyConfiguration: ConfigurationModel): void { + const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const change = this._configuration.compareAndUpdatePolicyConfiguration(policyConfiguration); + this.triggerConfigurationChange(change, previous, ConfigurationTarget.DEFAULT); + } + private onLocalUserConfigurationChanged(userConfiguration: ConfigurationModel): void { const previous = { data: this._configuration.toData(), workspace: this.workspace }; const change = this._configuration.compareAndUpdateLocalUserConfiguration(userConfiguration); diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 3e4e805c231..bf0d5f3adde 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -92,6 +92,11 @@ export const enum ConfigurationEditingErrorCode { */ ERROR_INVALID_CONFIGURATION, + /** + * Error when trying to write a policy configuration + */ + ERROR_POLICY_CONFIGURATION, + /** * Internal Error. */ @@ -359,6 +364,7 @@ export class ConfigurationEditingService { switch (error) { // API constraints + case ConfigurationEditingErrorCode.ERROR_POLICY_CONFIGURATION: return nls.localize('errorPolicyConfiguration', "Unable to write {0} because it is configured in system policy.", operation.key); case ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY: return nls.localize('errorUnknownKey', "Unable to write to {0} because {1} is not a registered configuration.", this.stringifyTarget(target), operation.key); case ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION: return nls.localize('errorInvalidWorkspaceConfigurationApplication', "Unable to write {0} to Workspace Settings. This setting can be written only into User settings.", operation.key); case ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_MACHINE: return nls.localize('errorInvalidWorkspaceConfigurationMachine', "Unable to write {0} to Workspace Settings. This setting can be written only into User settings.", operation.key); @@ -492,6 +498,10 @@ export class ConfigurationEditingService { private async validate(target: EditableConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationUpdateOverrides): Promise { + if (this.configurationService.inspect(operation.key).policyValue !== undefined) { + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_POLICY_CONFIGURATION, target, operation); + } + const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); const configurationScope = configurationProperties[operation.key]?.scope; diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 95a1d40ad77..807e96399df 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -98,6 +98,7 @@ export class Configuration extends BaseConfiguration { constructor( defaults: ConfigurationModel, + policy: ConfigurationModel, localUser: ConfigurationModel, remoteUser: ConfigurationModel, workspaceConfiguration: ConfigurationModel, @@ -105,7 +106,7 @@ export class Configuration extends BaseConfiguration { memoryConfiguration: ConfigurationModel, memoryConfigurationByResource: ResourceMap, private readonly _workspace?: Workspace) { - super(defaults, localUser, remoteUser, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource); + super(defaults, policy, localUser, remoteUser, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource); } override getValue(key: string | undefined, overrides: IConfigurationOverrides = {}): any { 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 228cc13113a..7f391a11ad3 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -155,14 +155,14 @@ suite('Workspace Configuration', () => { test('Test compare same configurations', () => { const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); - const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration1.updateDefaultConfiguration(defaultConfigurationModel); configuration1.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration1.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); configuration1.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.fontSize': 14 })); configuration1.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'editor.wordWrap': 'on' })); - const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration2.updateDefaultConfiguration(defaultConfigurationModel); configuration2.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration2.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); @@ -176,14 +176,14 @@ suite('Workspace Configuration', () => { test('Test compare different configurations', () => { const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); - const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration1.updateDefaultConfiguration(defaultConfigurationModel); configuration1.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration1.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); configuration1.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.fontSize': 14 })); configuration1.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'editor.wordWrap': 'on' })); - const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration2.updateDefaultConfiguration(defaultConfigurationModel); configuration2.updateLocalUserConfiguration(toConfigurationModel({ 'workbench.enableTabs': true, '[typescript]': { 'editor.insertSpaces': true } })); configuration2.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.fontSize': 11 })); From 94da754e273c33fdaa93f84c28240200cd1bc5d0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 10 May 2022 14:04:00 +0200 Subject: [PATCH 02/34] make sure policy value is undefined if empty --- src/vs/platform/configuration/common/configurationModels.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 03135dbdbb4..de218cadea1 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -490,7 +490,7 @@ export class Configuration { const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration; const defaultValue = overrides.overrideIdentifier ? this._defaultConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._defaultConfiguration.freeze().getValue(key); - const policyValue = this._policyConfiguration.freeze().getValue(key); + const policyValue = this._policyConfiguration.isEmpty() ? undefined : this._policyConfiguration.freeze().getValue(key); const userValue = overrides.overrideIdentifier ? this.userConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.userConfiguration.freeze().getValue(key); const userLocalValue = overrides.overrideIdentifier ? this.localUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.localUserConfiguration.freeze().getValue(key); const userRemoteValue = overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.remoteUserConfiguration.freeze().getValue(key); From 6086f78bfdb1a243d290137db5e731429c28dcd5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 10 May 2022 14:04:15 +0200 Subject: [PATCH 03/34] fix tests --- .../configuration/test/common/configurationModels.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 9887f80e184..859cd0c28a1 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -487,7 +487,7 @@ suite('Configuration', () => { const defaultConfigurationModel = parseConfigurationModel({ '[l1]': { 'a': 1 }, '[l2]': { 'b': 1 } }); const userConfigurationModel = parseConfigurationModel({ '[l3]': { 'a': 2 } }); const workspaceConfigurationModel = parseConfigurationModel({ '[l1]': { 'a': 3 }, '[l4]': { 'a': 3 } }); - const testObject: Configuration = new Configuration(defaultConfigurationModel, userConfigurationModel, new ConfigurationModel(), workspaceConfigurationModel); + const testObject: Configuration = new Configuration(defaultConfigurationModel, new ConfigurationModel(), userConfigurationModel, workspaceConfigurationModel); const { overrideIdentifiers } = testObject.inspect('a', {}, undefined); From 364240765b6cc5265c6341b87818a48c62ffa325 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 10 May 2022 14:48:02 +0200 Subject: [PATCH 04/34] refactor policy service --- .../configuration/common/configurations.ts | 13 +- .../policy/common/filePolicyService.ts | 91 ++++++++++++ src/vs/platform/policy/common/policy.ts | 135 +++++------------- 3 files changed, 133 insertions(+), 106 deletions(-) create mode 100644 src/vs/platform/policy/common/filePolicyService.ts diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index a86a1f50e49..ed855abf2ab 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -12,7 +12,8 @@ import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRID import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { PolicyModel } from 'vs/platform/policy/common/policy'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export class DefaultConfiguration extends Disposable { @@ -82,7 +83,7 @@ export class PolicyConfiguration extends Disposable { private readonly _onDidChangeConfiguration = this._register(new Emitter()); readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; - private readonly policies: PolicyModel; + private readonly policyService: IPolicyService; constructor( private readonly defaultConfiguration: DefaultConfiguration, @@ -91,7 +92,7 @@ export class PolicyConfiguration extends Disposable { logService: ILogService ) { super(); - this.policies = new PolicyModel(fileService, environmentService, logService); + this.policyService = environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) : new NullPolicyService(); } private _configurationModel: ConfigurationModel | undefined; @@ -105,7 +106,7 @@ export class PolicyConfiguration extends Disposable { if (!policyName) { continue; } - const value = this.policies.getPolicy(policyName); + const value = this.policyService.getPolicyValue(policyName); if (value === undefined) { continue; } @@ -118,8 +119,8 @@ export class PolicyConfiguration extends Disposable { } async initialize(): Promise { - await this.policies.initialize(); - this._register(this.policies.onDidChange(e => this.onDidChange())); + await this.policyService.initialize(); + this._register(this.policyService.onDidChange(e => this.onDidChange())); this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.onDidDefaultConfigurationChange(properties))); return this.reload(); } diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts new file mode 100644 index 00000000000..0cfa9e1588c --- /dev/null +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ThrottledDelayer } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Iterable } from 'vs/base/common/iterator'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { isObject } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; + +function keysDiff(a: Map, b: Map): string[] { + const result: string[] = []; + + for (const key of Iterable.concat(a.keys(), b.keys())) { + if (a.get(key) !== b.get(key)) { + result.push(key); + } + } + + return result; +} + +export class FilePolicyService extends Disposable implements IPolicyService { + + private policies: Policies = new Map(); + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + private readonly throttledDelayer = this._register(new ThrottledDelayer(500)); + + constructor( + private readonly file: URI, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService + ) { + super(); + + const onDidChangePolicyFile = Event.filter(fileService.onDidFilesChange, e => e.affects(file)); + this._register(fileService.watch(file)); + this._register(onDidChangePolicyFile(this.refresh, this)); + } + + async initialize(): Promise { + await this.doRefresh(); + } + + private refresh(): void { + this.throttledDelayer.trigger(() => this.doRefresh()); + } + + private async read(): Promise { + const policies: Policies = new Map(); + + try { + const content = await this.fileService.readFile(this.file); + const raw = JSON.parse(content.value.toString()); + + if (!isObject(raw)) { + throw new Error('Policy file isn\'t a JSON object'); + } + + for (const key of Object.keys(raw)) { + policies.set(key, raw[key]); + } + } catch (error) { + this.logService.error(`[FilePolicyService] Failed to read policies`, error); + } + + return policies; + } + + private async doRefresh(): Promise { + const policies = await this.read(); + const diff = keysDiff(this.policies, policies); + this.policies = policies; + + if (diff.length > 0) { + this._onDidChange.fire(diff); + } + } + + getPolicyValue(name: PolicyName): PolicyValue | undefined { + return this.policies.get(name); + } +} diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 5f9b1cfafe9..4febe66904f 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -3,110 +3,45 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ThrottledDelayer } from 'vs/base/common/async'; -import { IStringDictionary } from 'vs/base/common/collections'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; +import { Event } from 'vs/base/common/event'; +export type PolicyName = string; export type PolicyValue = string | boolean | number; -export type Policies = IStringDictionary; +export type Policies = Map; -export class PolicyModel extends Disposable { - readonly _serviceBrand: undefined; - - private readonly _onDidChange = new Emitter(); - readonly onDidChange = this._onDidChange.event; - - private policies: Policies = {}; - - constructor( - @IFileService private readonly fileService: IFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILogService private readonly logService: ILogService, - ) { - super(); - } - - async initialize(): Promise { - const policy = this.environmentService.policyFile ? new FilePolicy(this.environmentService.policyFile, this.fileService, this.logService) : new NullPolicy(); - this.policies = await policy.read(); - this._register(policy.onDidChange(({ changed, policies }) => { - this.policies = policies; - this._onDidChange.fire(changed); - })); - } - - getPolicy(name: string): PolicyValue | undefined { - return this.policies[name]; - } +export interface IPolicyService { + readonly onDidChange: Event; + initialize(): Promise; + getPolicyValue(name: PolicyName): PolicyValue | undefined; } -export interface IPolicy { - readonly onDidChange: Event<{ changed: string[]; policies: Policies }>; - read(): Promise; -} - -class FilePolicy extends Disposable implements IPolicy { - - private policies: Policies = {}; - - private readonly _onDidChange = new Emitter<{ changed: string[]; policies: Policies }>(); - readonly onDidChange = this._onDidChange.event; - - private readonly throttledDelayer = this._register(new ThrottledDelayer(500)); - - constructor( - private readonly file: URI, - @IFileService private readonly fileService: IFileService, - @ILogService private readonly logService: ILogService - ) { - super(); - this._register(fileService.watch(file)); - this._register(Event.filter(fileService.onDidFilesChange, e => e.affects(file))(e => this.throttledDelayer.trigger(() => this.onDidFileChange()))); - } - - async read(): Promise { - try { - const content = await this.fileService.readFile(this.file); - this.policies = JSON.parse(content.value.toString()); - } catch (error) { - this.logService.error(error); - this.policies = {}; - } - return this.policies; - } - - private async onDidFileChange(): Promise { - const old = this.policies; - await this.read(); - const changed = this.compare(old, this.policies); - if (changed.length > 0) { - this._onDidChange.fire({ changed, policies: this.policies }); - } - } - - private compare(old: Policies, current: Policies): string[] { - const changed: string[] = []; - for (const key of Object.keys(old)) { - if (old[key] !== current[key]) { - changed.push(key); - } - } - for (const key of Object.keys(current)) { - if (old[key] === undefined) { - changed.push(key); - } - } - return changed; - } - -} - -class NullPolicy extends Disposable implements IPolicy { +export class NullPolicyService implements IPolicyService { readonly onDidChange = Event.None; - async read(): Promise { return {}; } + async initialize() { } + getPolicyValue() { return undefined; } +} + +export class MultiPolicyService implements IPolicyService { + + readonly onDidChange: Event; + + constructor(private policyServices: readonly IPolicyService[]) { + this.onDidChange = Event.any(...policyServices.map(p => p.onDidChange)); + } + + async initialize() { + await Promise.all(this.policyServices.map(p => p.initialize())); + } + + getPolicyValue(name: PolicyName) { + for (const policyService of this.policyServices) { + const result = policyService.getPolicyValue(name); + + if (typeof result !== 'undefined') { + return result; + } + } + + return undefined; + } } From d4a3656ab354e17bc047de4590d0b9d58c3c73e5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 10 May 2022 20:07:24 +0200 Subject: [PATCH 05/34] smart/scoped update when policy values change --- .../common/configurationModels.ts | 14 ++- .../configuration/common/configurations.ts | 92 +++++++++++-------- 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index de218cadea1..ad704762f68 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -21,7 +21,7 @@ import { Workspace } from 'vs/platform/workspace/common/workspace'; export class ConfigurationModel implements IConfigurationModel { - private isFrozen: boolean = false; + private frozen: boolean = false; private readonly overrideConfigurations = new Map(); constructor( @@ -47,6 +47,10 @@ export class ConfigurationModel implements IConfigurationModel { return this._keys.length === 0 && Object.keys(this._contents).length === 0 && this._overrides.length === 0; } + isFrozen(): boolean { + return this.frozen; + } + getValue(section: string | undefined): V { return section ? getConfigurationValue(this.contents, section) : this.contents; } @@ -113,10 +117,14 @@ export class ConfigurationModel implements IConfigurationModel { } freeze(): ConfigurationModel { - this.isFrozen = true; + this.frozen = true; return this; } + clone(): ConfigurationModel { + return new ConfigurationModel(objects.deepClone(this.contents), [...this.keys], objects.deepClone(this.overrides)); + } + private createOverrideConfigurationModel(identifier: string): ConfigurationModel { const overrideContents = this.getContentsForOverrideIdentifer(identifier); @@ -161,7 +169,7 @@ export class ConfigurationModel implements IConfigurationModel { } private checkAndFreeze(data: T): T { - if (this.isFrozen && !Object.isFrozen(data)) { + if (this.frozen && !Object.isFrozen(data)) { return objects.deepFreeze(data); } return data; diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index ed855abf2ab..9e9603e9c61 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { coalesce } from 'vs/base/common/arrays'; import { IStringDictionary } from 'vs/base/common/collections'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { equals } from 'vs/base/common/objects'; import { addToValueTree, IOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; @@ -13,7 +15,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; -import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService, PolicyName } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export class DefaultConfiguration extends Disposable { @@ -84,6 +86,10 @@ export class PolicyConfiguration extends Disposable { readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; private readonly policyService: IPolicyService; + private readonly policyNamesToKeys = new Map(); + + private _configurationModel = new ConfigurationModel(); + get configurationModel() { return this._configurationModel; } constructor( private readonly defaultConfiguration: DefaultConfiguration, @@ -95,50 +101,56 @@ export class PolicyConfiguration extends Disposable { this.policyService = environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) : new NullPolicyService(); } - private _configurationModel: ConfigurationModel | undefined; - get configurationModel(): ConfigurationModel { - if (!this._configurationModel) { - const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - const keys: string[] = []; - const contents: any = Object.create(null); - for (const key of this.defaultConfiguration.configurationModel.keys) { - const policyName = configurationProperties[key].policy?.name; - if (!policyName) { - continue; - } - const value = this.policyService.getPolicyValue(policyName); - if (value === undefined) { - continue; - } - keys.push(key); - addToValueTree(contents, key, value, message => console.error(`Conflict in policy settings: ${message}`)); - } - this._configurationModel = new ConfigurationModel(contents, keys, []); - } + async initialize(): Promise { + await this.policyService.initialize(); + this.updateKeys(this.defaultConfiguration.configurationModel.keys, false); + this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.updateKeys(properties, true))); return this._configurationModel; } - async initialize(): Promise { - await this.policyService.initialize(); - this._register(this.policyService.onDidChange(e => this.onDidChange())); - this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.onDidDefaultConfigurationChange(properties))); - return this.reload(); - } - - reload(): ConfigurationModel { - this._configurationModel = undefined; - return this.configurationModel; - } - - private onDidDefaultConfigurationChange(properties: string[]): void { + private updateKeys(keys: string[], trigger: boolean): void { const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - if (properties.some(key => configurationProperties[key].policy?.name)) { - this.onDidChange(); + const keyPolicyNamePairs: [string, PolicyName][] = coalesce(keys.map(key => { + const policyName = configurationProperties[key].policy?.name; + return policyName ? [key, policyName] : undefined; + })); + this.update(keyPolicyNamePairs, trigger); + } + + private onDidChangePolicies(policyNames: readonly PolicyName[]): void { + const keyPolicyNamePairs: [string, PolicyName][] = coalesce(policyNames.map(policyName => { + const key = this.policyNamesToKeys.get(policyName); + return key ? [key, policyName] : undefined; + })); + this.update(keyPolicyNamePairs, true); + } + + private update(keyPolicyNamePairs: [string, PolicyName][], trigger: boolean): void { + if (!keyPolicyNamePairs.length) { + return; + } + + const updated: string[] = []; + this._configurationModel = this._configurationModel.isFrozen() ? this._configurationModel.clone() : this._configurationModel; + const isEmpty = this._configurationModel.isEmpty(); + for (const [key, policyName] of keyPolicyNamePairs) { + this.policyNamesToKeys.set(policyName, key); + const value = this.policyService.getPolicyValue(policyName); + if (!isEmpty && equals(this._configurationModel.getValue(key), value)) { + continue; + } + if (value === undefined) { + this._configurationModel.removeValue(key); + } else { + this._configurationModel.setValue(key, value); + } + updated.push(key); + } + + if (updated.length && trigger) { + this._onDidChangeConfiguration.fire(this._configurationModel); } } - private onDidChange(): void { - this._onDidChangeConfiguration.fire(this.reload()); - } - } From ce60d548e2cced5d3cf868e4a929ed0f8f9d8823 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 May 2022 16:16:11 +0200 Subject: [PATCH 06/34] - add tests - fix scenarios causing tests failures - check for duplicate policy names --- .../common/configurationRegistry.ts | 34 +++- .../configuration/common/configurations.ts | 71 +++---- .../test/common/configurationRegistry.test.ts | 26 +++ .../test/common/policyConfiguration.test.ts | 179 ++++++++++++++++++ .../policy/common/filePolicyService.ts | 4 +- src/vs/platform/policy/common/policy.ts | 6 + .../api/common/configurationExtensionPoint.ts | 4 +- .../browser/configurationService.ts | 11 +- .../configurationEditingService.test.ts | 37 +++- .../test/browser/configurationService.test.ts | 30 ++- 10 files changed, 351 insertions(+), 51 deletions(-) create mode 100644 src/vs/platform/configuration/test/common/policyConfiguration.test.ts diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 1f424d52cb7..11331ac829b 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -11,6 +11,7 @@ import * as types from 'vs/base/common/types'; import * as nls from 'vs/nls'; import { getLanguageTagSettingPlainKey } from 'vs/platform/configuration/common/configuration'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { PolicyName } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export enum EditPresentationTypes { @@ -89,6 +90,11 @@ export interface IConfigurationRegistry { */ getConfigurationProperties(): IStringDictionary; + /** + * Return all configurations by policy name + */ + getPolicyConfigurations(): Map; + /** * Returns all excluded configurations settings of all configuration nodes contributed to this registry. */ @@ -127,12 +133,12 @@ export const enum ConfigurationScope { MACHINE_OVERRIDABLE, } -export interface PolicyConfiguration { +export interface IPolicy { /** * The policy name. */ - readonly name: string; + readonly name: PolicyName; /** * The Code version in which this policy was introduced. @@ -193,7 +199,7 @@ export interface IConfigurationPropertySchema extends IJSONSchema { * When specified, this setting's value can always be overwritten by * a system-wide policy. */ - policy?: PolicyConfiguration; + policy?: IPolicy; } export interface IExtensionInfo { @@ -245,6 +251,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { private readonly defaultLanguageConfigurationOverridesNode: IConfigurationNode; private readonly configurationContributors: IConfigurationNode[]; private readonly configurationProperties: IStringDictionary; + private readonly policyConfigurations: Map; private readonly excludedConfigurationProperties: IStringDictionary; private readonly resourceLanguageSettingsSchema: IJSONSchema; private readonly overrideIdentifiers = new Set(); @@ -265,6 +272,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.configurationContributors = [this.defaultLanguageConfigurationOverridesNode]; this.resourceLanguageSettingsSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true }; this.configurationProperties = {}; + this.policyConfigurations = new Map(); this.excludedConfigurationProperties = {}; contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); @@ -397,6 +405,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { if (configuration.properties) { for (const key in configuration.properties) { properties.push(key); + const property = this.configurationProperties[key]; + if (property?.policy?.name) { + this.policyConfigurations.delete(property.policy.name); + } delete this.configurationProperties[key]; this.removeFromSchema(key, configuration.properties[key]); } @@ -421,12 +433,12 @@ class ConfigurationRegistry implements IConfigurationRegistry { let properties = configuration.properties; if (properties) { for (let key in properties) { - if (validate && validateProperty(key)) { + const property: IRegisteredConfigurationPropertySchema = properties[key]; + if (validate && validateProperty(key, property)) { delete properties[key]; continue; } - const property: IRegisteredConfigurationPropertySchema = properties[key]; property.source = extensionInfo; // update default value @@ -449,6 +461,9 @@ class ConfigurationRegistry implements IConfigurationRegistry { continue; } else { this.configurationProperties[key] = properties[key]; + if (properties[key].policy?.name) { + this.policyConfigurations.set(properties[key].policy!.name, key); + } } if (!properties[key].deprecationMessage && properties[key].markdownDeprecationMessage) { @@ -477,6 +492,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { return this.configurationProperties; } + getPolicyConfigurations(): Map { + return this.policyConfigurations; + } + getExcludedConfigurationProperties(): IStringDictionary { return this.excludedConfigurationProperties; } @@ -647,7 +666,7 @@ export function getDefaultValue(type: string | string[] | undefined): any { const configurationRegistry = new ConfigurationRegistry(); Registry.add(Extensions.Configuration, configurationRegistry); -export function validateProperty(property: string): string | null { +export function validateProperty(property: string, schema: IRegisteredConfigurationPropertySchema): string | null { if (!property.trim()) { return nls.localize('config.property.empty', "Cannot register an empty property"); } @@ -657,6 +676,9 @@ export function validateProperty(property: string): string | null { if (configurationRegistry.getConfigurationProperties()[property] !== undefined) { return nls.localize('config.property.duplicate', "Cannot register '{0}'. This property is already registered.", property); } + if (schema.policy?.name && configurationRegistry.getPolicyConfigurations().get(schema.policy?.name) !== undefined) { + return nls.localize('config.policy.duplicate', "Cannot register '{0}'. The associated policy {1} is already registered with {2}.", property, schema.policy?.name, configurationRegistry.getPolicyConfigurations().get(schema.policy?.name)); + } return null; } diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 9e9603e9c61..a9cf1f256f9 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -15,7 +15,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; -import { IPolicyService, NullPolicyService, PolicyName } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export class DefaultConfiguration extends Disposable { @@ -86,7 +86,6 @@ export class PolicyConfiguration extends Disposable { readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; private readonly policyService: IPolicyService; - private readonly policyNamesToKeys = new Map(); private _configurationModel = new ConfigurationModel(); get configurationModel() { return this._configurationModel; } @@ -103,53 +102,55 @@ export class PolicyConfiguration extends Disposable { async initialize(): Promise { await this.policyService.initialize(); - this.updateKeys(this.defaultConfiguration.configurationModel.keys, false); + this.update(this.defaultConfiguration.configurationModel.keys, false); this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); - this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.updateKeys(properties, true))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.update(properties, true))); return this._configurationModel; } - private updateKeys(keys: string[], trigger: boolean): void { - const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - const keyPolicyNamePairs: [string, PolicyName][] = coalesce(keys.map(key => { - const policyName = configurationProperties[key].policy?.name; - return policyName ? [key, policyName] : undefined; - })); - this.update(keyPolicyNamePairs, trigger); + async reload(): Promise { + await this.policyService.refresh(); + this.update(this.defaultConfiguration.configurationModel.keys, false); + return this._configurationModel; } private onDidChangePolicies(policyNames: readonly PolicyName[]): void { - const keyPolicyNamePairs: [string, PolicyName][] = coalesce(policyNames.map(policyName => { - const key = this.policyNamesToKeys.get(policyName); - return key ? [key, policyName] : undefined; - })); - this.update(keyPolicyNamePairs, true); + const policyConfigurations = Registry.as(Extensions.Configuration).getPolicyConfigurations(); + const keys = coalesce(policyNames.map(policyName => policyConfigurations.get(policyName))); + this.update(keys, true); } - private update(keyPolicyNamePairs: [string, PolicyName][], trigger: boolean): void { - if (!keyPolicyNamePairs.length) { - return; - } + private update(keys: string[], trigger: boolean): void { + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const changed: [string, PolicyValue | undefined][] = []; + const wasEmpty = this._configurationModel.isEmpty(); - const updated: string[] = []; - this._configurationModel = this._configurationModel.isFrozen() ? this._configurationModel.clone() : this._configurationModel; - const isEmpty = this._configurationModel.isEmpty(); - for (const [key, policyName] of keyPolicyNamePairs) { - this.policyNamesToKeys.set(policyName, key); - const value = this.policyService.getPolicyValue(policyName); - if (!isEmpty && equals(this._configurationModel.getValue(key), value)) { - continue; - } - if (value === undefined) { - this._configurationModel.removeValue(key); + for (const key of keys) { + const policyName = configurationProperties[key]?.policy?.name; + if (policyName) { + const policyValue = this.policyService.getPolicyValue(policyName); + if (wasEmpty ? policyValue !== undefined : !equals(this._configurationModel.getValue(key), policyValue)) { + changed.push([key, policyValue]); + } } else { - this._configurationModel.setValue(key, value); + if (this._configurationModel.getValue(key) !== undefined) { + changed.push([key, undefined]); + } } - updated.push(key); } - if (updated.length && trigger) { - this._onDidChangeConfiguration.fire(this._configurationModel); + if (changed.length) { + this._configurationModel = this._configurationModel.isFrozen() ? this._configurationModel.clone() : this._configurationModel; + for (const [key, policyValue] of changed) { + if (policyValue === undefined) { + this._configurationModel.removeValue(key); + } else { + this._configurationModel.setValue(key, policyValue); + } + } + if (trigger) { + this._onDidChangeConfiguration.fire(this._configurationModel); + } } } diff --git a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts index 83314c83ad3..8e836674dae 100644 --- a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts +++ b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts @@ -50,4 +50,30 @@ suite('ConfigurationRegistry', () => { assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 2, c: 3 }); }); + + test('registering multiple settings with same policy', async () => { + configurationRegistry.registerConfiguration({ + 'id': '_test_default', + 'type': 'object', + 'properties': { + 'policy1': { + 'type': 'object', + policy: { + name: 'policy', + minimumVersion: '1.0.0' + } + }, + 'policy2': { + 'type': 'object', + policy: { + name: 'policy', + minimumVersion: '1.0.0' + } + } + } + }); + const actual = configurationRegistry.getConfigurationProperties(); + assert.ok(actual['policy1'] !== undefined); + assert.ok(actual['policy2'] === undefined); + }); }); diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts new file mode 100644 index 00000000000..3f6e15bdd0c --- /dev/null +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -0,0 +1,179 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { deepClone } from 'vs/base/common/objects'; + +suite('PolicyConfiguration', () => { + + let testObject: PolicyConfiguration; + let fileService: IFileService; + const policyFile = URI.file('policyFile').with({ scheme: 'vscode-tests' }); + const disposables = new DisposableStore(); + const policyConfigurationNode: IConfigurationNode = { + 'id': 'policyConfiguration', + 'order': 1, + 'title': 'a', + 'type': 'object', + 'properties': { + 'policy.settingA': { + 'type': 'boolean', + 'default': true, + policy: { + name: 'PolicySettingA', + minimumVersion: '1.0.0', + } + }, + 'nonPolicy.setting': { + 'type': 'boolean', + 'default': true + } + } + }; + + suiteSetup(() => Registry.as(Extensions.Configuration).registerConfiguration(policyConfigurationNode)); + suiteTeardown(() => Registry.as(Extensions.Configuration).deregisterConfigurations([policyConfigurationNode])); + + setup(async () => { + const defaultConfiguration = disposables.add(new DefaultConfiguration()); + await defaultConfiguration.initialize(); + fileService = disposables.add(new FileService(new NullLogService())); + const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); + fileService.registerProvider(policyFile.scheme, diskFileSystemProvider); + testObject = disposables.add(new PolicyConfiguration(defaultConfiguration, fileService, { policyFile } as IEnvironmentService, new NullLogService())); + }); + + teardown(() => disposables.clear()); + + test('initialize: with policies', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + + await testObject.initialize(); + const acutal = testObject.configurationModel; + + assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('initialize: no policies', async () => { + await testObject.initialize(); + const acutal = testObject.configurationModel; + + assert.deepStrictEqual(acutal.keys, []); + assert.deepStrictEqual(acutal.overrides, []); + assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + }); + + test('initialize: with policies but not registered', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false }))); + + await testObject.initialize(); + const acutal = testObject.configurationModel; + + assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('change: when policy is added', async () => { + await testObject.initialize(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await promise; + + const acutal = testObject.configurationModel; + assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('change: when policy is updated', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await testObject.initialize(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': true }))); + await promise; + + const acutal = testObject.configurationModel; + assert.strictEqual(acutal.getValue('policy.settingA'), true); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('change: when policy is removed', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await testObject.initialize(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({}))); + await promise; + + const acutal = testObject.configurationModel; + assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, []); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('change: when policy setting is registered', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingB': false }))); + await testObject.initialize(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + policyConfigurationNode.properties!['policy.settingB'] = { + 'type': 'boolean', + 'default': true, + policy: { + name: 'PolicySettingB', + minimumVersion: '1.0.0', + } + }; + Registry.as(Extensions.Configuration).registerConfiguration(deepClone(policyConfigurationNode)); + await promise; + + const acutal = testObject.configurationModel; + assert.strictEqual(acutal.getValue('policy.settingB'), false); + assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, ['policy.settingB']); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('change: when policy setting is deregistered', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await testObject.initialize(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + Registry.as(Extensions.Configuration).deregisterConfigurations([policyConfigurationNode]); + await promise; + + const acutal = testObject.configurationModel; + assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, []); + assert.deepStrictEqual(acutal.overrides, []); + }); + +}); diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 0cfa9e1588c..4779fe77c0a 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -50,8 +50,8 @@ export class FilePolicyService extends Disposable implements IPolicyService { await this.doRefresh(); } - private refresh(): void { - this.throttledDelayer.trigger(() => this.doRefresh()); + async refresh(): Promise { + await this.throttledDelayer.trigger(() => this.doRefresh()); } private async read(): Promise { diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 4febe66904f..6963bf22081 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -12,12 +12,14 @@ export type Policies = Map; export interface IPolicyService { readonly onDidChange: Event; initialize(): Promise; + refresh(): Promise; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { readonly onDidChange = Event.None; async initialize() { } + async refresh() { } getPolicyValue() { return undefined; } } @@ -33,6 +35,10 @@ export class MultiPolicyService implements IPolicyService { await Promise.all(this.policyServices.map(p => p.initialize())); } + async refresh() { + await Promise.all(this.policyServices.map(p => p.refresh())); + } + getPolicyValue(name: PolicyName) { for (const policyService of this.policyServices) { const result = policyService.getPolicyValue(name); diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 406661573d2..17d42a088fa 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -204,7 +204,8 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { configuration.properties = {}; } for (let key in properties) { - const message = validateProperty(key); + const propertyConfiguration = properties[key]; + const message = validateProperty(key, propertyConfiguration); if (message) { delete properties[key]; extension.collector.warn(message); @@ -215,7 +216,6 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { extension.collector.warn(nls.localize('config.property.duplicate', "Cannot register '{0}'. This property is already registered.", key)); continue; } - const propertyConfiguration = properties[key]; if (!isObject(propertyConfiguration)) { delete properties[key]; extension.collector.error(nls.localize('invalid.property', "configuration.properties property '{0}' must be an object", key)); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index a4c11121290..0db722acbc1 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -342,6 +342,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat async reloadConfiguration(target?: ConfigurationTarget | IWorkspaceFolder): Promise { if (target === undefined) { + this.reloadDefaultConfiguration(); + await this.reloadPolicyConfiguration(); const { local, remote } = await this.reloadUserConfiguration(); await this.reloadWorkspaceConfiguration(); await this.loadConfiguration(local, remote); @@ -355,7 +357,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat switch (target) { case ConfigurationTarget.DEFAULT: - await this.reloadDefaultConfiguration(); + this.reloadDefaultConfiguration(); + await this.reloadPolicyConfiguration(); return; case ConfigurationTarget.USER: { @@ -596,10 +599,14 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return { local, remote }; } - private async reloadDefaultConfiguration(): Promise { + private reloadDefaultConfiguration(): void { this.onDefaultConfigurationChanged(this.defaultConfiguration.reload()); } + private async reloadPolicyConfiguration(): Promise { + this.onPolicyConfigurationChanged(await this.policyConfiguration.reload()); + } + private async reloadUserConfiguration(): Promise<{ local: ConfigurationModel; remote: ConfigurationModel }> { const [local, remote] = await Promise.all([this.reloadLocalUserConfiguration(true), this.reloadRemoteUserConfiguration(true)]); return { local, remote }; diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 7d7e3cb1267..2fba810064f 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -37,8 +37,9 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentService'; -import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { hash } from 'vs/base/common/hash'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -52,7 +53,7 @@ export class ConfigurationCache implements IConfigurationCache { suite('ConfigurationEditingService', () => { let instantiationService: TestInstantiationService; - let environmentService: BrowserWorkbenchEnvironmentService; + let environmentService: IWorkbenchEnvironmentService; let fileService: IFileService; let workspaceService: WorkspaceService; let testObject: ConfigurationEditingService; @@ -76,6 +77,14 @@ suite('ConfigurationEditingService', () => { 'configurationEditing.service.testSettingThree': { 'type': 'string', 'default': 'isSet' + }, + 'configurationEditing.service.policySetting': { + 'type': 'string', + 'default': 'isSet', + policy: { + name: 'configurationEditing.service.policySetting', + minimumVersion: '1.0.0', + } } } }); @@ -92,12 +101,17 @@ suite('ConfigurationEditingService', () => { instantiationService = workbenchInstantiationService(undefined, disposables); environmentService = TestEnvironmentService; + environmentService.policyFile = joinPath(workspaceFolder, 'policies.json'); instantiationService.stub(IEnvironmentService, environmentService); const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null)); disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService)))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + await workspaceService.initialize({ + id: hash(workspaceFolder.toString()).toString(16), + uri: workspaceFolder + }); instantiationService.stub(IWorkspaceContextService, workspaceService); await workspaceService.initialize(getSingleFolderWorkspaceIdentifier(workspaceFolder)); @@ -180,6 +194,25 @@ suite('ConfigurationEditingService', () => { assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.'); }); + test('errors cases - ERROR_POLICY_CONFIGURATION', async () => { + await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationEditing.service.policySetting": "policyValue" }')); + await instantiationService.get(IConfigurationService).reloadConfiguration(); + try { + await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.policySetting', value: 'value' }, { donotNotifyError: true }); + } catch (error) { + assert.strictEqual(error.code, ConfigurationEditingErrorCode.ERROR_POLICY_CONFIGURATION); + return; + } + assert.fail('Should fail with ERROR_POLICY_CONFIGURATION'); + }); + + test('write policy setting - when not set', async () => { + await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.policySetting', value: 'value' }, { donotNotifyError: true }); + const contents = await fileService.readFile(environmentService.settingsResource); + const parsed = json.parse(contents.value.toString()); + assert.strictEqual(parsed['configurationEditing.service.policySetting'], 'value'); + }); + test('write one setting - empty file', async () => { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }); const contents = await fileService.readFile(environmentService.settingsResource); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 352fd8e9d7e..5a207dccd6d 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -669,7 +669,7 @@ suite('WorkspaceService - Initialization', () => { suite('WorkspaceConfigurationService - Folder', () => { - let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: BrowserWorkbenchEnvironmentService; + let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IWorkbenchEnvironmentService; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); const disposables: DisposableStore = new DisposableStore(); @@ -708,6 +708,14 @@ suite('WorkspaceConfigurationService - Folder', () => { 'default': 'isSet', restricted: true }, + 'configurationService.folder.policySetting': { + 'type': 'string', + 'default': 'isSet', + policy: { + name: 'configurationService.folder.policySetting', + minimumVersion: '1.0.0', + } + }, } }); @@ -731,6 +739,7 @@ suite('WorkspaceConfigurationService - Folder', () => { const instantiationService = workbenchInstantiationService(undefined, disposables); environmentService = TestEnvironmentService; + environmentService.policyFile = joinPath(folder, 'policies.json'); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); @@ -750,7 +759,7 @@ suite('WorkspaceConfigurationService - Folder', () => { teardown(() => disposables.clear()); test('defaults', () => { - assert.deepStrictEqual(testObject.getValue('configurationService'), { 'folder': { 'applicationSetting': 'isSet', 'machineSetting': 'isSet', 'machineOverridableSetting': 'isSet', 'testSetting': 'isSet', 'languageSetting': 'isSet', 'restrictedSetting': 'isSet' } }); + assert.deepStrictEqual(testObject.getValue('configurationService'), { 'folder': { 'applicationSetting': 'isSet', 'machineSetting': 'isSet', 'machineOverridableSetting': 'isSet', 'testSetting': 'isSet', 'languageSetting': 'isSet', 'restrictedSetting': 'isSet', 'policySetting': 'isSet' } }); }); test('globals override defaults', async () => { @@ -956,6 +965,23 @@ suite('WorkspaceConfigurationService - Folder', () => { assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); }); + test('policy value override all', async () => { + await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.policySetting": "userValue" }')); + await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.policySetting": "workspaceValue" }')); + await testObject.reloadConfiguration(); + assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'policyValue'); + assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, 'policyValue'); + }); + + test('policy settings when policy value is not set', async () => { + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.policySetting": "userValue" }')); + await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.policySetting": "workspaceValue" }')); + await testObject.reloadConfiguration(); + assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'workspaceValue'); + assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, undefined); + }); + test('reload configuration emits events after global configuraiton changes', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); const target = sinon.spy(); From 0d291672e501c0f6939910ae8c9e9a118f3fde19 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 May 2022 16:30:49 +0200 Subject: [PATCH 07/34] - add test for change event when policy value changes - fix the model. create model always --- src/vs/platform/configuration/common/configurations.ts | 7 ++++++- .../test/browser/configurationService.test.ts | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index a9cf1f256f9..353ad2070d3 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -140,7 +140,11 @@ export class PolicyConfiguration extends Disposable { } if (changed.length) { - this._configurationModel = this._configurationModel.isFrozen() ? this._configurationModel.clone() : this._configurationModel; + const old = this._configurationModel; + this._configurationModel = new ConfigurationModel(); + for (const key of old.keys) { + this._configurationModel.setValue(key, old.getValue(key)); + } for (const [key, policyValue] of changed) { if (policyValue === undefined) { this._configurationModel.removeValue(key); @@ -154,4 +158,5 @@ export class PolicyConfiguration extends Disposable { } } + } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 5a207dccd6d..7f980fd1d1d 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -982,6 +982,15 @@ suite('WorkspaceConfigurationService - Folder', () => { assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, undefined); }); + test('policy change should trigger change event ', async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); + const result = await promise; + assert.deepStrictEqual(result.affectedKeys, ['configurationService.folder.policySetting']); + assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'policyValue'); + assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, 'policyValue'); + }); + test('reload configuration emits events after global configuraiton changes', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); const target = sinon.spy(); From 2752a8237260b3fcf7e329338fa55c38ef2a951e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 May 2022 16:34:59 +0200 Subject: [PATCH 08/34] refresh should not be throttled as it is requested explicitly --- src/vs/platform/policy/common/filePolicyService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 4779fe77c0a..1fbec3f40cd 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -43,7 +43,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { const onDidChangePolicyFile = Event.filter(fileService.onDidFilesChange, e => e.affects(file)); this._register(fileService.watch(file)); - this._register(onDidChangePolicyFile(this.refresh, this)); + this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.doRefresh()))); } async initialize(): Promise { @@ -51,7 +51,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { } async refresh(): Promise { - await this.throttledDelayer.trigger(() => this.doRefresh()); + await this.doRefresh(); } private async read(): Promise { From 78cabb9419909a5cdd2a219b518e1696dc1e0907 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 May 2022 16:50:11 +0200 Subject: [PATCH 09/34] make tests roboust --- .../test/common/policyConfiguration.test.ts | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index 3f6e15bdd0c..44a8c34894a 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -38,6 +38,14 @@ suite('PolicyConfiguration', () => { minimumVersion: '1.0.0', } }, + 'policy.settingB': { + 'type': 'boolean', + 'default': true, + policy: { + name: 'PolicySettingB', + minimumVersion: '1.0.0', + } + }, 'nonPolicy.setting': { 'type': 'boolean', 'default': true @@ -66,6 +74,7 @@ suite('PolicyConfiguration', () => { const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA']); assert.deepStrictEqual(acutal.overrides, []); @@ -78,32 +87,36 @@ suite('PolicyConfiguration', () => { assert.deepStrictEqual(acutal.keys, []); assert.deepStrictEqual(acutal.overrides, []); assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); }); test('initialize: with policies but not registered', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false, 'PolicySettingC': false }))); await testObject.initialize(); const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('policy.settingB'), false); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); - assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.keys, ['policy.settingA', 'policy.settingB']); assert.deepStrictEqual(acutal.overrides, []); }); test('change: when policy is added', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false, 'PolicySettingC': false }))); await promise; const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('policy.settingB'), false); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); - assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.keys, ['policy.settingA', 'policy.settingB']); assert.deepStrictEqual(acutal.overrides, []); }); @@ -117,6 +130,7 @@ suite('PolicyConfiguration', () => { const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), true); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA']); assert.deepStrictEqual(acutal.overrides, []); @@ -132,21 +146,22 @@ suite('PolicyConfiguration', () => { const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, []); assert.deepStrictEqual(acutal.overrides, []); }); test('change: when policy setting is registered', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingB': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingC': false }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); - policyConfigurationNode.properties!['policy.settingB'] = { + policyConfigurationNode.properties!['policy.settingC'] = { 'type': 'boolean', 'default': true, policy: { - name: 'PolicySettingB', + name: 'PolicySettingC', minimumVersion: '1.0.0', } }; @@ -154,10 +169,11 @@ suite('PolicyConfiguration', () => { await promise; const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingB'), false); + assert.strictEqual(acutal.getValue('policy.settingC'), false); assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); - assert.deepStrictEqual(acutal.keys, ['policy.settingB']); + assert.deepStrictEqual(acutal.keys, ['policy.settingC']); assert.deepStrictEqual(acutal.overrides, []); }); @@ -171,6 +187,7 @@ suite('PolicyConfiguration', () => { const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, []); assert.deepStrictEqual(acutal.overrides, []); From b2a380b59d788e234de12243955c07f539477971 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Wed, 11 May 2022 09:43:00 -0700 Subject: [PATCH 10/34] Show policy info in Settings editor --- .../browser/media/settingsEditor2.css | 10 ++++- .../preferences/browser/settingsTree.ts | 37 +++++++++++++++++-- .../preferences/browser/settingsTreeModels.ts | 13 +++++-- .../preferences/common/preferences.ts | 1 + .../preferences/common/preferencesModels.ts | 1 + 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 92733560d7b..bc6fdef7bc5 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -399,13 +399,15 @@ -webkit-user-select: text; } -.settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-untrusted > .setting-item-contents .setting-item-trust-description { +.settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-untrusted > .setting-item-contents .setting-item-trust-description, +.settings-editor > .settings-body .settings-tree-container .setting-item > .setting-item-contents .setting-item-policy-description { display: flex; font-weight: 600; margin: 6px 0 12px 0; } -.settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-untrusted > .setting-item-contents .setting-item-trust-description > span { +.settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-untrusted > .setting-item-contents .setting-item-trust-description > span, +.settings-editor > .settings-body .settings-tree-container .setting-item > .setting-item-contents .setting-item-policy-description > span { padding-right: 5px; } @@ -413,6 +415,10 @@ color: var(--workspace-trust-state-untrusted-color) !important; } +.settings-editor > .settings-body .settings-tree-container .setting-item > .setting-item-contents .setting-item-policy-description > span.codicon.codicon-lock { + color: var(--organization-policy-icon-color) !important; +} + .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-validation-message { display: none; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 7b551ab1466..40cf05955e4 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -584,6 +584,7 @@ interface ISettingItemTemplate extends IDisposableTemplate { containerElement: HTMLElement; categoryElement: HTMLElement; labelElement: SimpleIconLabel; + policyWarningElement: HTMLElement; descriptionElement: HTMLElement; controlElement: HTMLElement; deprecationWarningElement: HTMLElement; @@ -605,6 +606,7 @@ type ISettingNumberItemTemplate = ISettingTextItemTemplate; interface ISettingEnumItemTemplate extends ISettingItemTemplate { selectBox: SelectBox; + selectElement: HTMLSelectElement | null; enumDescriptionElement: HTMLElement; } @@ -781,9 +783,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const categoryElement = DOM.append(labelCategoryContainer, $('span.setting-item-category')); const labelElementContainer = DOM.append(labelCategoryContainer, $('span.setting-item-label')); const labelElement = new SimpleIconLabel(labelElementContainer); - const miscLabel = new SettingsTreeMiscLabel(titleElement); - const descriptionElement = DOM.append(container, $('.setting-item-description')); const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); modifiedIndicatorElement.title = localize('modified', "The setting has been configured in the current scope."); @@ -795,6 +795,14 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const toDispose = new DisposableStore(); + const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); + const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); + toDispose.add(attachStylerCallback(this._themeService, { editorErrorForeground }, colors => { + policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorErrorForeground?.toString() || ''); + })); + const element = DOM.append(policyWarningElement, $('span')); + element.textContent = localize('policyLabel', "This setting is not configurable due to your organization's policy."); + const toolbarContainer = DOM.append(container, $('.setting-toolbar-container')); const toolbar = this.renderSettingToolbar(toolbarContainer); @@ -805,6 +813,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre containerElement: container, categoryElement, labelElement, + policyWarningElement, descriptionElement, controlElement, deprecationWarningElement, @@ -908,6 +917,8 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre template.miscLabel.updateSyncIgnored(element, this.ignoredSettings); })); + template.policyWarningElement.hidden = !element.setting.hasPolicyValue; + this.updateSettingTabbable(element, template); template.elementDisposables.add(element.onDidChangeTabbable(() => { this.updateSettingTabbable(element, template); @@ -1538,6 +1549,7 @@ abstract class AbstractSettingTextRenderer extends AbstractSettingRenderer imple template.onChange = undefined; template.inputBox.value = dataElement.value; template.inputBox.setAriaLabel(dataElement.setting.key); + template.inputBox.inputElement.disabled = !!dataElement.setting.hasPolicyValue; template.onChange = value => { if (!renderValidations(dataElement, template, false)) { onChange(value); @@ -1633,6 +1645,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre const template: ISettingEnumItemTemplate = { ...common, selectBox, + selectElement, enumDescriptionElement }; @@ -1704,6 +1717,10 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre } }; + if (template.selectElement) { + template.selectElement.disabled = !!dataElement.setting.hasPolicyValue; + } + template.enumDescriptionElement.innerText = ''; } } @@ -1757,6 +1774,7 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT template.onChange = undefined; template.inputBox.value = dataElement.value; template.inputBox.setAriaLabel(dataElement.setting.key); + template.inputBox.setEnabled(!dataElement.setting.hasPolicyValue); template.onChange = value => { if (!renderValidations(dataElement, template, false)) { onChange(nullNumParseFn(value)); @@ -1789,7 +1807,6 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); modifiedIndicatorElement.title = localize('modified', "The setting has been configured in the current scope."); - const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); const toDispose = new DisposableStore(); @@ -1819,6 +1836,14 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const toolbar = this.renderSettingToolbar(toolbarContainer); toDispose.add(toolbar); + const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); + const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); + toDispose.add(attachStylerCallback(this._themeService, { editorErrorForeground }, colors => { + policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorErrorForeground?.toString() || ''); + })); + const element = DOM.append(policyWarningElement, $('span')); + element.textContent = localize('policyLabel', "This setting is not configurable due to your organization's policy."); + const template: ISettingBoolItemTemplate = { toDispose, elementDisposables: new DisposableStore(), @@ -1828,6 +1853,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre labelElement, controlElement, checkbox, + policyWarningElement, descriptionElement, deprecationWarningElement, miscLabel, @@ -1852,6 +1878,11 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre template.onChange = undefined; template.checkbox.checked = dataElement.value; template.checkbox.setTitle(dataElement.setting.key); + if (dataElement.setting.hasPolicyValue) { + template.checkbox.disable(); + } else { + template.checkbox.enable(); + } template.onChange = onChange; } } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 6c4169e6b7c..cdf7ede3a9e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -181,7 +181,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { } update(inspectResult: IInspectResult, isWorkspaceTrusted: boolean): void { - const { isConfigured, inspected, targetSelector, inspectedLanguageOverrides, languageSelector } = inspectResult; + let { isConfigured, inspected, targetSelector, inspectedLanguageOverrides, languageSelector } = inspectResult; switch (targetSelector) { case 'workspaceFolderValue': @@ -213,20 +213,25 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { } } - if (languageSelector && this.languageOverrideValues.has(languageSelector)) { + const policyValue = inspected.policyValue; + if (inspected.policyValue) { + isConfigured = false; // The user did not manually configure the setting themselves. + displayValue = policyValue; + this.scopeValue = policyValue; + this.defaultValue = inspected.defaultValue; + } else if (languageSelector && this.languageOverrideValues.has(languageSelector)) { const overrideValues = this.languageOverrideValues.get(languageSelector)!; // In the worst case, go back to using the previous display value. // Also, sometimes the override is in the form of a default value override, so consider that second. displayValue = (isConfigured ? overrideValues[targetSelector] : overrideValues.defaultValue) ?? displayValue; - this.value = displayValue; this.scopeValue = isConfigured && overrideValues[targetSelector]; this.defaultValue = overrideValues.defaultValue ?? inspected.defaultValue; } else { - this.value = displayValue; this.scopeValue = isConfigured && inspected[targetSelector]; this.defaultValue = inspected.defaultValue; } + this.value = displayValue; this.isConfigured = isConfigured; if (isConfigured || this.setting.tags || this.tags || this.setting.restricted) { // Don't create an empty Set for all 1000 settings, only if needed diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 5aaf1198fe4..c33637b5934 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -91,6 +91,7 @@ export interface ISetting { isLanguageTagSetting?: boolean; categoryOrder?: number; categoryLabel?: string; + hasPolicyValue?: boolean; } export interface IExtensionSetting extends ISetting { diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 000a74b77ca..cd9c8d784ef 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -741,6 +741,7 @@ export class DefaultSettings extends Disposable { tags: prop.tags, disallowSyncIgnore: prop.disallowSyncIgnore, restricted: prop.restricted, + hasPolicyValue: !!prop.policy, extensionInfo: extensionInfo, deprecationMessage: prop.markdownDeprecationMessage || prop.deprecationMessage, deprecationMessageIsMarkdown: !!prop.markdownDeprecationMessage, From 3982c0ed0b9db59dad3129516bf450a923aacf97 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 May 2022 12:28:08 +0200 Subject: [PATCH 11/34] - change policy file location next to argv.json - introduce __enable-file-policy` flag to enable file based policies --- src/vs/platform/environment/common/argv.ts | 2 +- .../environment/common/environmentService.ts | 12 +++++++++++- src/vs/platform/environment/node/argv.ts | 2 +- src/vs/platform/policy/common/filePolicyService.ts | 6 ++++-- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 3834247b45b..1c903e2d25f 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -88,7 +88,7 @@ export interface NativeParsedArgs { 'sync'?: 'on' | 'off'; '__sandbox'?: boolean; 'logsPath'?: string; - 'policy-file'?: string; + '__enable-file-policy'?: boolean; // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index d3214f7036f..1d7d75afbb9 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -238,7 +238,17 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron get disableWorkspaceTrust(): boolean { return !!this.args['disable-workspace-trust']; } @memoize - get policyFile(): URI | undefined { return this.args['policy-file'] ? URI.file(this.args['policy-file']) : undefined; } + get policyFile(): URI | undefined { + if (this.args['__enable-file-policy']) { + const vscodePortable = env['VSCODE_PORTABLE']; + if (vscodePortable) { + return URI.file(join(vscodePortable, 'policy.json')); + } + + return joinPath(this.userHome, this.productService.dataFolderName, 'policy.json'); + } + return undefined; + } get args(): NativeParsedArgs { return this._args; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 0a69e9cd2c4..239aaaf7045 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -73,7 +73,6 @@ export const OPTIONS: OptionDescriptions> = { 'disable-extensions': { type: 'boolean', deprecates: ['disableExtensions'], cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, 'disable-extension': { type: 'string[]', cat: 't', args: 'ext-id', description: localize('disableExtension', "Disable an extension.") }, 'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off."), args: ['on | off'] }, - 'policy-file': { type: 'string', cat: 't', description: localize('policyFile', "Path of the file defining policies") }, 'inspect-extensions': { type: 'string', deprecates: ['debugPluginHost'], args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, 'inspect-brk-extensions': { type: 'string', deprecates: ['debugBrkPluginHost'], args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, @@ -126,6 +125,7 @@ export const OPTIONS: OptionDescriptions> = { 'open-devtools': { type: 'boolean' }, '__sandbox': { type: 'boolean' }, 'logsPath': { type: 'string' }, + '__enable-file-policy': { type: 'boolean' }, // chromium flags 'no-proxy-server': { type: 'boolean' }, diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 1fbec3f40cd..44d3105dad3 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -9,7 +9,7 @@ import { Iterable } from 'vs/base/common/iterator'; import { Disposable } from 'vs/base/common/lifecycle'; import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { IFileService } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; @@ -69,7 +69,9 @@ export class FilePolicyService extends Disposable implements IPolicyService { policies.set(key, raw[key]); } } catch (error) { - this.logService.error(`[FilePolicyService] Failed to read policies`, error); + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { + this.logService.error(`[FilePolicyService] Failed to read policies`, error); + } } return policies; From 281d6155e5b03cba5523b83fe504ee9f8635039b Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Thu, 12 May 2022 17:15:10 -0700 Subject: [PATCH 12/34] Tweak UI and fix hasPolicy detection --- .../browser/media/settingsEditor2.css | 4 ++++ .../preferences/browser/settingsTree.ts | 24 +++++++++---------- .../preferences/browser/settingsTreeModels.ts | 11 ++++++--- .../preferences/common/preferences.ts | 1 - .../preferences/common/preferencesModels.ts | 1 - 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index bc6fdef7bc5..7b4684b2961 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -406,6 +406,10 @@ margin: 6px 0 12px 0; } +.settings-editor > .settings-body .settings-tree-container .setting-item > .setting-item-contents .setting-item-policy-description[hidden] { + display: none; +} + .settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-untrusted > .setting-item-contents .setting-item-trust-description > span, .settings-editor > .settings-body .settings-tree-container .setting-item > .setting-item-contents .setting-item-policy-description > span { padding-right: 5px; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 40cf05955e4..673de4bf5aa 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -36,7 +36,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { editorBackground, editorErrorForeground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorBackground, editorErrorForeground, editorInfoForeground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; @@ -797,11 +797,11 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); - toDispose.add(attachStylerCallback(this._themeService, { editorErrorForeground }, colors => { - policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorErrorForeground?.toString() || ''); + toDispose.add(attachStylerCallback(this._themeService, { editorInfoForeground }, colors => { + policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorInfoForeground?.toString() || ''); })); const element = DOM.append(policyWarningElement, $('span')); - element.textContent = localize('policyLabel', "This setting is not configurable due to your organization's policy."); + element.textContent = localize('policyLabel', "This setting is managed by your organization."); const toolbarContainer = DOM.append(container, $('.setting-toolbar-container')); const toolbar = this.renderSettingToolbar(toolbarContainer); @@ -917,7 +917,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre template.miscLabel.updateSyncIgnored(element, this.ignoredSettings); })); - template.policyWarningElement.hidden = !element.setting.hasPolicyValue; + template.policyWarningElement.hidden = !element.hasPolicyValue; this.updateSettingTabbable(element, template); template.elementDisposables.add(element.onDidChangeTabbable(() => { @@ -1549,7 +1549,7 @@ abstract class AbstractSettingTextRenderer extends AbstractSettingRenderer imple template.onChange = undefined; template.inputBox.value = dataElement.value; template.inputBox.setAriaLabel(dataElement.setting.key); - template.inputBox.inputElement.disabled = !!dataElement.setting.hasPolicyValue; + template.inputBox.inputElement.disabled = !!dataElement.hasPolicyValue; template.onChange = value => { if (!renderValidations(dataElement, template, false)) { onChange(value); @@ -1718,7 +1718,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre }; if (template.selectElement) { - template.selectElement.disabled = !!dataElement.setting.hasPolicyValue; + template.selectElement.disabled = !!dataElement.hasPolicyValue; } template.enumDescriptionElement.innerText = ''; @@ -1774,7 +1774,7 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT template.onChange = undefined; template.inputBox.value = dataElement.value; template.inputBox.setAriaLabel(dataElement.setting.key); - template.inputBox.setEnabled(!dataElement.setting.hasPolicyValue); + template.inputBox.setEnabled(!dataElement.hasPolicyValue); template.onChange = value => { if (!renderValidations(dataElement, template, false)) { onChange(nullNumParseFn(value)); @@ -1838,11 +1838,11 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); - toDispose.add(attachStylerCallback(this._themeService, { editorErrorForeground }, colors => { - policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorErrorForeground?.toString() || ''); + toDispose.add(attachStylerCallback(this._themeService, { editorInfoForeground }, colors => { + policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorInfoForeground?.toString() || ''); })); const element = DOM.append(policyWarningElement, $('span')); - element.textContent = localize('policyLabel', "This setting is not configurable due to your organization's policy."); + element.textContent = localize('policyLabel', "This setting is managed by your organization."); const template: ISettingBoolItemTemplate = { toDispose, @@ -1878,7 +1878,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre template.onChange = undefined; template.checkbox.checked = dataElement.value; template.checkbox.setTitle(dataElement.setting.key); - if (dataElement.setting.hasPolicyValue) { + if (dataElement.hasPolicyValue) { template.checkbox.disable(); } else { template.checkbox.enable(); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index cdf7ede3a9e..6e0c5b34b43 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -138,6 +138,11 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { */ isUntrusted = false; + /** + * Whether the setting is under a policy that blocks all changes. + */ + hasPolicyValue = false; + tags?: Set; overriddenScopeList: string[] = []; languageOverrideValues: Map> = new Map>(); @@ -213,11 +218,11 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { } } - const policyValue = inspected.policyValue; if (inspected.policyValue) { + this.hasPolicyValue = true; isConfigured = false; // The user did not manually configure the setting themselves. - displayValue = policyValue; - this.scopeValue = policyValue; + displayValue = inspected.policyValue; + this.scopeValue = inspected.policyValue; this.defaultValue = inspected.defaultValue; } else if (languageSelector && this.languageOverrideValues.has(languageSelector)) { const overrideValues = this.languageOverrideValues.get(languageSelector)!; diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index c33637b5934..5aaf1198fe4 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -91,7 +91,6 @@ export interface ISetting { isLanguageTagSetting?: boolean; categoryOrder?: number; categoryLabel?: string; - hasPolicyValue?: boolean; } export interface IExtensionSetting extends ISetting { diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index cd9c8d784ef..000a74b77ca 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -741,7 +741,6 @@ export class DefaultSettings extends Disposable { tags: prop.tags, disallowSyncIgnore: prop.disallowSyncIgnore, restricted: prop.restricted, - hasPolicyValue: !!prop.policy, extensionInfo: extensionInfo, deprecationMessage: prop.markdownDeprecationMessage || prop.deprecationMessage, deprecationMessageIsMarkdown: !!prop.markdownDeprecationMessage, From d90d07adef925477222c83ae090a233544ab35e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 16 May 2022 17:46:39 +0200 Subject: [PATCH 13/34] wip: policy --- .eslintrc.json | 1 + .../configuration/common/configurations.ts | 5 +- src/vs/platform/policy/common/policy.ts | 6 -- src/vs/platform/policy/common/policyIpc.ts | 73 +++++++++++++++++ .../policy/node/vscode-policy-watcher.d.ts | 30 +++++++ .../policy/node/windowsPolicyService.ts | 79 +++++++++++++++++++ 6 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 src/vs/platform/policy/common/policyIpc.ts create mode 100644 src/vs/platform/policy/node/vscode-policy-watcher.d.ts create mode 100644 src/vs/platform/policy/node/windowsPolicyService.ts diff --git a/.eslintrc.json b/.eslintrc.json index 22157840d84..da169cdcc0c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -248,6 +248,7 @@ "url", "util", "v8-inspect-profiler", + "vscode-policy-watcher", "vscode-proxy-agent", "vscode-regexpp", "vscode-textmate", diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 353ad2070d3..dad918f94e1 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -109,7 +109,10 @@ export class PolicyConfiguration extends Disposable { } async reload(): Promise { - await this.policyService.refresh(); + if (this.policyService instanceof FilePolicyService) { + await this.policyService.refresh(); + } + this.update(this.defaultConfiguration.configurationModel.keys, false); return this._configurationModel; } diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 6963bf22081..4febe66904f 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -12,14 +12,12 @@ export type Policies = Map; export interface IPolicyService { readonly onDidChange: Event; initialize(): Promise; - refresh(): Promise; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { readonly onDidChange = Event.None; async initialize() { } - async refresh() { } getPolicyValue() { return undefined; } } @@ -35,10 +33,6 @@ export class MultiPolicyService implements IPolicyService { await Promise.all(this.policyServices.map(p => p.initialize())); } - async refresh() { - await Promise.all(this.policyServices.map(p => p.refresh())); - } - getPolicyValue(name: PolicyName) { for (const policyService of this.policyServices) { const result = policyService.getPolicyValue(name); diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts new file mode 100644 index 00000000000..00a0913f693 --- /dev/null +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; + +export class PolicyChannel implements IServerChannel { + + private readonly disposables = new DisposableStore(); + + constructor(private service: IPolicyService) { } + + listen(_: unknown, event: string): Event { + switch (event) { + case 'onDidChange': return Event.map( + this.service.onDidChange, + names => new Map( + names + .map(name => [name, this.service.getPolicyValue(name)]) + .filter(pair => pair[1] !== undefined) as [PolicyName, PolicyValue][]), + this.disposables + ); + } + + throw new Error(`Event not found: ${event}`); + } + + call(_: unknown, command: string): Promise { + switch (command) { + case 'initialize': return this.service.initialize(); + } + + throw new Error(`Call not found: ${command}`); + } + + dispose() { + this.disposables.dispose(); + } +} + +export class PolicyChannelClient implements IPolicyService { + + declare readonly _serviceBrand: undefined; + + private policies: Policies = new Map(); + + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + + constructor(private readonly channel: IChannel) { + this.channel.listen('onDidChange')(policies => { + for (const [name, value] of policies) { + if (value === undefined) { + this.policies.delete(name); + } else { + this.policies.set(name, value); + } + } + }); + } + + initialize(): Promise { + return this.channel.call('initialize'); + } + + getPolicyValue(name: PolicyName): PolicyValue | undefined { + return this.policies.get(name); + } +} diff --git a/src/vs/platform/policy/node/vscode-policy-watcher.d.ts b/src/vs/platform/policy/node/vscode-policy-watcher.d.ts new file mode 100644 index 00000000000..c305f178822 --- /dev/null +++ b/src/vs/platform/policy/node/vscode-policy-watcher.d.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode-policy-watcher' { + interface Watcher { + dispose(): void; + } + + interface Policies { + [policyName: string]: { + type: string + }; + } + + interface PolicyValues { + [policyName: string]: string | number | boolean; + } + + function createWatcher( + productName: string, + policies: Policies, + onDidChange: (update: PolicyValues) => void + ): Watcher; + + namespace createWatcher { } + + export = createWatcher; +} diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts new file mode 100644 index 00000000000..64e7b8bb7d6 --- /dev/null +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import * as createWatcher from 'vscode-policy-watcher'; +import { IStringDictionary } from 'vs/base/common/collections'; + +export class WindowsPolicyService extends Disposable implements IPolicyService { + + private policies: Policies = new Map(); + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + constructor( + @IProductService private readonly productService: IProductService + ) { + super(); + } + + initialize(): Promise { + return new Promise(c => { + if (!this.productService.win32RegValueName) { + return; + } + + const policies: IStringDictionary<{ type: string }> = {}; + const configRegistry = Registry.as(Extensions.Configuration); + + for (const configuration of configRegistry.getConfigurations()) { + if (configuration.properties) { + for (const key in configuration.properties) { + const config = configuration.properties[key]; + const policy = config.policy; + + if (policy) { + if (config.type !== 'string' && config.type !== 'number') { + console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); + continue; + } + + policies[policy.name] = { type: config.type }; + } + } + } + } + + let first = true; + + this._register(createWatcher(this.productService.win32RegValueName, policies, update => () => { + for (const key in update) { + this.policies.set(key, update[key]); + } + + if (first) { + first = false; + c(); + } else { + this._onDidChange.fire(Object.keys(update)); + } + })); + }); + } + + async refresh(): Promise { + // NOOP + } + + getPolicyValue(name: PolicyName): PolicyValue | undefined { + return this.policies.get(name); + } +} From 9a9240dc36f15365efb854a6e6d770eb7d07731b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 May 2022 18:40:55 +0200 Subject: [PATCH 14/34] do not support refresh for policies --- .../configuration/common/configurations.ts | 6 ------ .../platform/policy/common/filePolicyService.ts | 10 +++------- src/vs/platform/policy/common/policy.ts | 6 ------ .../configuration/browser/configurationService.ts | 6 ------ .../browser/configurationEditingService.test.ts | 4 +++- .../test/browser/configurationService.test.ts | 15 +++------------ 6 files changed, 9 insertions(+), 38 deletions(-) diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 353ad2070d3..2dd2290898e 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -108,12 +108,6 @@ export class PolicyConfiguration extends Disposable { return this._configurationModel; } - async reload(): Promise { - await this.policyService.refresh(); - this.update(this.defaultConfiguration.configurationModel.keys, false); - return this._configurationModel; - } - private onDidChangePolicies(policyNames: readonly PolicyName[]): void { const policyConfigurations = Registry.as(Extensions.Configuration).getPolicyConfigurations(); const keys = coalesce(policyNames.map(policyName => policyConfigurations.get(policyName))); diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 44d3105dad3..e3f34bafc3d 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -43,15 +43,11 @@ export class FilePolicyService extends Disposable implements IPolicyService { const onDidChangePolicyFile = Event.filter(fileService.onDidFilesChange, e => e.affects(file)); this._register(fileService.watch(file)); - this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.doRefresh()))); + this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.refresh()))); } async initialize(): Promise { - await this.doRefresh(); - } - - async refresh(): Promise { - await this.doRefresh(); + await this.refresh(); } private async read(): Promise { @@ -77,7 +73,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { return policies; } - private async doRefresh(): Promise { + private async refresh(): Promise { const policies = await this.read(); const diff = keysDiff(this.policies, policies); this.policies = policies; diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 6963bf22081..4febe66904f 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -12,14 +12,12 @@ export type Policies = Map; export interface IPolicyService { readonly onDidChange: Event; initialize(): Promise; - refresh(): Promise; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { readonly onDidChange = Event.None; async initialize() { } - async refresh() { } getPolicyValue() { return undefined; } } @@ -35,10 +33,6 @@ export class MultiPolicyService implements IPolicyService { await Promise.all(this.policyServices.map(p => p.initialize())); } - async refresh() { - await Promise.all(this.policyServices.map(p => p.refresh())); - } - getPolicyValue(name: PolicyName) { for (const policyService of this.policyServices) { const result = policyService.getPolicyValue(name); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 0db722acbc1..752eec13a27 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -343,7 +343,6 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat async reloadConfiguration(target?: ConfigurationTarget | IWorkspaceFolder): Promise { if (target === undefined) { this.reloadDefaultConfiguration(); - await this.reloadPolicyConfiguration(); const { local, remote } = await this.reloadUserConfiguration(); await this.reloadWorkspaceConfiguration(); await this.loadConfiguration(local, remote); @@ -358,7 +357,6 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat switch (target) { case ConfigurationTarget.DEFAULT: this.reloadDefaultConfiguration(); - await this.reloadPolicyConfiguration(); return; case ConfigurationTarget.USER: { @@ -603,10 +601,6 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.onDefaultConfigurationChanged(this.defaultConfiguration.reload()); } - private async reloadPolicyConfiguration(): Promise { - this.onPolicyConfigurationChanged(await this.policyConfiguration.reload()); - } - private async reloadUserConfiguration(): Promise<{ local: ConfigurationModel; remote: ConfigurationModel }> { const [local, remote] = await Promise.all([this.reloadLocalUserConfiguration(true), this.reloadRemoteUserConfiguration(true)]); return { local, remote }; diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 2fba810064f..4b54d2e7a5d 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -6,6 +6,7 @@ import * as sinon from 'sinon'; import * as assert from 'assert'; import * as json from 'vs/base/common/json'; +import { Event } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -195,8 +196,9 @@ suite('ConfigurationEditingService', () => { }); test('errors cases - ERROR_POLICY_CONFIGURATION', async () => { + const promise = Event.toPromise(instantiationService.get(IConfigurationService).onDidChangeConfiguration); await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationEditing.service.policySetting": "policyValue" }')); - await instantiationService.get(IConfigurationService).reloadConfiguration(); + await promise; try { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.policySetting', value: 'value' }, { donotNotifyError: true }); } catch (error) { diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 7f980fd1d1d..9236183075d 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -966,10 +966,10 @@ suite('WorkspaceConfigurationService - Folder', () => { }); test('policy value override all', async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); - await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.policySetting": "userValue" }')); - await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.policySetting": "workspaceValue" }')); - await testObject.reloadConfiguration(); + const result = await promise; + assert.deepStrictEqual(result.affectedKeys, ['configurationService.folder.policySetting']); assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'policyValue'); assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, 'policyValue'); }); @@ -982,15 +982,6 @@ suite('WorkspaceConfigurationService - Folder', () => { assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, undefined); }); - test('policy change should trigger change event ', async () => { - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); - const result = await promise; - assert.deepStrictEqual(result.affectedKeys, ['configurationService.folder.policySetting']); - assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'policyValue'); - assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, 'policyValue'); - }); - test('reload configuration emits events after global configuraiton changes', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); const target = sinon.spy(); From 70b7af01e8a639681b63cb32a5cabc5c80428120 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 16 May 2022 21:57:18 +0200 Subject: [PATCH 15/34] wire up policy service --- .../sharedProcess/sharedProcessMain.ts | 8 +- src/vs/code/electron-main/app.ts | 7 ++ src/vs/code/electron-main/main.ts | 20 +++-- src/vs/code/node/cliProcessMain.ts | 9 ++- .../common/configurationService.ts | 8 +- .../configuration/common/configurations.ts | 12 +-- .../test/common/configurationService.test.ts | 27 +++---- .../test/common/policyConfiguration.test.ts | 7 +- .../policy/common/filePolicyService.ts | 2 + src/vs/platform/policy/common/policy.ts | 8 ++ .../policy/node/vscode-policy-watcher.d.ts | 30 -------- .../policy/node/windowsPolicyService.ts | 73 ++++++++++--------- .../test/common/userDataSyncClient.ts | 3 +- .../node/remoteExtensionHostAgentCli.ts | 3 +- src/vs/server/node/serverServices.ts | 3 +- src/vs/workbench/browser/web.main.ts | 3 +- .../electron-sandbox/desktop.main.ts | 20 ++++- .../browser/configurationService.ts | 4 +- .../configurationEditingService.test.ts | 3 +- .../test/browser/configurationService.test.ts | 19 ++--- 20 files changed, 146 insertions(+), 123 deletions(-) delete mode 100644 src/vs/platform/policy/node/vscode-policy-watcher.d.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index aa4c7ad67e7..6b07e7c7ddd 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -100,6 +100,8 @@ import { InspectProfilingService as V8InspectProfilingService } from 'vs/platfor import { IV8InspectProfilingService } from 'vs/platform/profiling/common/profiling'; import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; +import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; class SharedProcessMain extends Disposable { @@ -180,6 +182,10 @@ class SharedProcessMain extends Disposable { const mainProcessService = new MessagePortMainProcessService(this.server, mainRouter); services.set(IMainProcessService, mainProcessService); + // Policies + const policyService = new PolicyChannelClient(mainProcessService.getChannel('policy')); + services.set(IPolicyService, policyService); + // Environment const environmentService = new SharedProcessEnvironmentService(this.configuration.args, productService); services.set(INativeEnvironmentService, environmentService); @@ -222,7 +228,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService)); services.set(IConfigurationService, configurationService); // Storage (global access only) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4bc4a432ceb..64085aad356 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -99,6 +99,8 @@ import { IWorkspacesHistoryMainService, WorkspacesHistoryMainService } from 'vs/ import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IWorkspacesManagementMainService, WorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { CredentialsNativeMainService } from 'vs/platform/credentials/electron-main/credentialsMainService'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { PolicyChannel } from 'vs/platform/policy/common/policyIpc'; /** * The main VS Code application. There will only ever be one instance, @@ -695,6 +697,11 @@ export class CodeApplication extends Disposable { const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsMainService), { disableMarshalling: true }); this.mainProcessNodeIpcServer.registerChannel('diagnostics', diagnosticsChannel); + // Policies (main & shared process) + const policyChannel = new PolicyChannel(accessor.get(IPolicyService)); + mainProcessElectronServer.registerChannel('policy', policyChannel); + sharedProcessClient.then(client => client.registerChannel('policy', policyChannel)); + // Local Files const diskFileSystemProvider = this.fileService.getProvider(Schemas.file); assertType(diskFileSystemProvider instanceof DiskFileSystemProvider); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 7b95aaad618..f31b96ebece 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -62,6 +62,8 @@ import { IStateMainService } from 'vs/platform/state/electron-main/state'; import { StateMainService } from 'vs/platform/state/electron-main/stateMainService'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; +import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; /** * The main VS Code entry point. @@ -89,13 +91,13 @@ class CodeMain { setUnexpectedErrorHandler(err => console.error(err)); // Create services - const [instantiationService, instanceEnvironment, environmentMainService, configurationService, stateMainService, bufferLogService, productService] = this.createServices(); + const [instantiationService, instanceEnvironment, environmentMainService, policyService, configurationService, stateMainService, bufferLogService, productService] = this.createServices(); try { // Init services try { - await this.initServices(environmentMainService, configurationService, stateMainService); + await this.initServices(environmentMainService, policyService, configurationService, stateMainService); } catch (error) { // Show a dialog for errors that can be resolved by the user @@ -137,7 +139,7 @@ class CodeMain { } } - private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateMainService, BufferLogService, IProductService] { + private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, IPolicyService, ConfigurationService, StateMainService, BufferLogService, IProductService] { const services = new ServiceCollection(); // Product @@ -166,8 +168,12 @@ class CodeMain { // Logger services.set(ILoggerService, new LoggerService(logService, fileService)); + // Policy + const policyService = isWindows ? new WindowsPolicyService(productService) : new NullPolicyService(); + services.set(IPolicyService, policyService); + // Configuration - const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, environmentMainService, logService); + const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, policyService); services.set(IConfigurationService, configurationService); // Lifecycle @@ -192,7 +198,7 @@ class CodeMain { // Protocol services.set(IProtocolMainService, new SyncDescriptor(ProtocolMainService)); - return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateMainService, bufferLogService, productService]; + return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, policyService, configurationService, stateMainService, bufferLogService, productService]; } private patchEnvironment(environmentMainService: IEnvironmentMainService): IProcessEnvironment { @@ -212,7 +218,7 @@ class CodeMain { return instanceEnvironment; } - private initServices(environmentMainService: IEnvironmentMainService, configurationService: ConfigurationService, stateMainService: StateMainService): Promise { + private initServices(environmentMainService: IEnvironmentMainService, policyService: IPolicyService, configurationService: ConfigurationService, stateMainService: StateMainService): Promise { return Promises.settled([ // Environment service (paths) @@ -226,6 +232,8 @@ class CodeMain { environmentMainService.backupHome ].map(path => path ? FSPromises.mkdir(path, { recursive: true }) : undefined)), + policyService.initialize(), + // Configuration service configurationService.initialize(), diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 6ae96fc2047..2217a4b70b1 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -11,6 +11,7 @@ import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/err import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isAbsolute, join } from 'vs/base/common/path'; +import { isWindows } from 'vs/base/common/platform'; import { cwd } from 'vs/base/common/process'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -39,6 +40,8 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ConsoleLogger, getLogLevel, ILogger, ILogService, LogLevel, MultiplexLogService } from 'vs/platform/log/common/log'; import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; +import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; @@ -128,8 +131,12 @@ class CliMain extends Disposable { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); fileService.registerProvider(Schemas.file, diskFileSystemProvider); + // Policy + const policyService = isWindows ? new WindowsPolicyService(productService) : new NullPolicyService(); + services.set(IPolicyService, policyService); + // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService)); services.set(IConfigurationService, configurationService); // Init config diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index b3ff50a6d0c..3d0422d38c1 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -11,9 +11,8 @@ import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationChangeEvent, ConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { @@ -31,12 +30,11 @@ export class ConfigurationService extends Disposable implements IConfigurationSe constructor( private readonly settingsResource: URI, fileService: IFileService, - environmentService: IEnvironmentService, - logService: ILogService, + policyService: IPolicyService, ) { super(); this.defaultConfiguration = this._register(new DefaultConfiguration()); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, fileService, environmentService, logService)); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel()); diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index dad918f94e1..dc2fd2e6079 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -11,11 +11,8 @@ import { equals } from 'vs/base/common/objects'; import { addToValueTree, IOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; -import { IPolicyService, NullPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export class DefaultConfiguration extends Disposable { @@ -85,19 +82,14 @@ export class PolicyConfiguration extends Disposable { private readonly _onDidChangeConfiguration = this._register(new Emitter()); readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; - private readonly policyService: IPolicyService; - private _configurationModel = new ConfigurationModel(); get configurationModel() { return this._configurationModel; } constructor( private readonly defaultConfiguration: DefaultConfiguration, - fileService: IFileService, - environmentService: IEnvironmentService, - logService: ILogService + @IPolicyService private readonly policyService: IPolicyService ) { super(); - this.policyService = environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) : new NullPolicyService(); } async initialize(): Promise { diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index 04635ed013c..d49af5f41f6 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -12,19 +12,17 @@ import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { NullLogService } from 'vs/platform/log/common/log'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; suite('ConfigurationService', () => { let fileService: IFileService; - let environmentService: IEnvironmentService; let settingsResource: URI; const disposables: DisposableStore = new DisposableStore(); @@ -33,14 +31,13 @@ suite('ConfigurationService', () => { const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); settingsResource = URI.file('settings.json'); - environmentService = new TestInstantiationService().mock(IEnvironmentService) as IEnvironmentService; }); teardown(() => disposables.clear()); test('simple', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -53,7 +50,7 @@ suite('ConfigurationService', () => { test('config gets flattened', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await testObject.initialize(); const config = testObject.getValue<{ testworkbench: { @@ -72,7 +69,7 @@ suite('ConfigurationService', () => { test('error case does not explode', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString(',,,,')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -82,7 +79,7 @@ suite('ConfigurationService', () => { }); test('missing file does not explode', async () => { - const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string }>(); @@ -91,7 +88,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file does not exist', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await testObject.initialize(); return new Promise((c, e) => { disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => { @@ -104,7 +101,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file exists', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); await testObject.initialize(); @@ -120,7 +117,7 @@ suite('ConfigurationService', () => { test('reloadConfiguration', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await testObject.initialize(); let config = testObject.getValue<{ foo: string; @@ -159,7 +156,7 @@ suite('ConfigurationService', () => { } }); - let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, environmentService, new NullLogService())); + let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService())); await testObject.initialize(); let setting = testObject.getValue(); @@ -167,7 +164,7 @@ suite('ConfigurationService', () => { assert.strictEqual(setting.configuration.service.testSetting, 'isSet'); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); setting = testObject.getValue(); @@ -195,7 +192,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); testObject.initialize(); let res = testObject.inspect('something.missing'); @@ -230,7 +227,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); testObject.initialize(); let res = testObject.inspect('lookup.service.testNullSetting'); diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index 44a8c34894a..784334ffa7c 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -8,7 +8,6 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; @@ -17,11 +16,14 @@ import { Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platf import { Registry } from 'vs/platform/registry/common/platform'; import { VSBuffer } from 'vs/base/common/buffer'; import { deepClone } from 'vs/base/common/objects'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; suite('PolicyConfiguration', () => { let testObject: PolicyConfiguration; let fileService: IFileService; + let policyService: IPolicyService; const policyFile = URI.file('policyFile').with({ scheme: 'vscode-tests' }); const disposables = new DisposableStore(); const policyConfigurationNode: IConfigurationNode = { @@ -62,7 +64,8 @@ suite('PolicyConfiguration', () => { fileService = disposables.add(new FileService(new NullLogService())); const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); fileService.registerProvider(policyFile.scheme, diskFileSystemProvider); - testObject = disposables.add(new PolicyConfiguration(defaultConfiguration, fileService, { policyFile } as IEnvironmentService, new NullLogService())); + policyService = new FilePolicyService(policyFile, fileService, new NullLogService()); + testObject = disposables.add(new PolicyConfiguration(defaultConfiguration, policyService)); }); teardown(() => disposables.clear()); diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 44d3105dad3..c93a15b365f 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -27,6 +27,8 @@ function keysDiff(a: Map, b: Map): string[] { export class FilePolicyService extends Disposable implements IPolicyService { + readonly _serviceBrand: undefined; + private policies: Policies = new Map(); private readonly _onDidChange = new Emitter(); diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 4febe66904f..3d7af47c262 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -4,18 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export type PolicyName = string; export type PolicyValue = string | boolean | number; export type Policies = Map; +export const IPolicyService = createDecorator('policy'); + export interface IPolicyService { + readonly _serviceBrand: undefined; + readonly onDidChange: Event; initialize(): Promise; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { + readonly _serviceBrand: undefined; readonly onDidChange = Event.None; async initialize() { } getPolicyValue() { return undefined; } @@ -23,6 +29,8 @@ export class NullPolicyService implements IPolicyService { export class MultiPolicyService implements IPolicyService { + readonly _serviceBrand: undefined; + readonly onDidChange: Event; constructor(private policyServices: readonly IPolicyService[]) { diff --git a/src/vs/platform/policy/node/vscode-policy-watcher.d.ts b/src/vs/platform/policy/node/vscode-policy-watcher.d.ts deleted file mode 100644 index c305f178822..00000000000 --- a/src/vs/platform/policy/node/vscode-policy-watcher.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode-policy-watcher' { - interface Watcher { - dispose(): void; - } - - interface Policies { - [policyName: string]: { - type: string - }; - } - - interface PolicyValues { - [policyName: string]: string | number | boolean; - } - - function createWatcher( - productName: string, - policies: Policies, - onDidChange: (update: PolicyValues) => void - ): Watcher; - - namespace createWatcher { } - - export = createWatcher; -} diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 64e7b8bb7d6..77550a47c3b 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -9,12 +9,15 @@ import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/p import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import * as createWatcher from 'vscode-policy-watcher'; +import { createWatcher } from 'vscode-policy-watcher'; import { IStringDictionary } from 'vs/base/common/collections'; export class WindowsPolicyService extends Disposable implements IPolicyService { + readonly _serviceBrand: undefined; + private policies: Policies = new Map(); + private init: Promise | undefined; private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; @@ -26,51 +29,51 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { } initialize(): Promise { - return new Promise(c => { - if (!this.productService.win32RegValueName) { - return; - } + if (!this.init) { + this.init = new Promise(c => { + if (!this.productService.win32RegValueName) { + return; + } - const policies: IStringDictionary<{ type: string }> = {}; - const configRegistry = Registry.as(Extensions.Configuration); + const policies: IStringDictionary<{ type: 'string' | 'number' }> = {}; + const configRegistry = Registry.as(Extensions.Configuration); - for (const configuration of configRegistry.getConfigurations()) { - if (configuration.properties) { - for (const key in configuration.properties) { - const config = configuration.properties[key]; - const policy = config.policy; + for (const configuration of configRegistry.getConfigurations()) { + if (configuration.properties) { + for (const key in configuration.properties) { + const config = configuration.properties[key]; + const policy = config.policy; - if (policy) { - if (config.type !== 'string' && config.type !== 'number') { - console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); - continue; + if (policy) { + if (config.type !== 'string' && config.type !== 'number') { + console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); + continue; + } + + policies[policy.name] = { type: config.type }; } - - policies[policy.name] = { type: config.type }; } } } - } - let first = true; + let first = true; - this._register(createWatcher(this.productService.win32RegValueName, policies, update => () => { - for (const key in update) { - this.policies.set(key, update[key]); - } + this._register(createWatcher(this.productService.win32RegValueName, policies, update => () => { + for (const key in update) { + this.policies.set(key, update[key]!); + } - if (first) { - first = false; - c(); - } else { - this._onDidChange.fire(Object.keys(update)); - } - })); - }); - } + if (first) { + first = false; + c(); + } else { + this._onDidChange.fire(Object.keys(update)); + } + })); + }); + } - async refresh(): Promise { - // NOOP + return this.init; } getPolicyValue(name: PolicyName): PolicyValue | undefined { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 5d3ba1f21bd..2d888a524c9 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -41,6 +41,7 @@ import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/pl import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreManagementService, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; export class UserDataSyncClient extends Disposable { @@ -86,7 +87,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IStorageService, this._register(new InMemoryStorageService())); - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService())); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); this.instantiationService.stub(IUriIdentityService, this.instantiationService.createInstance(UriIdentityService)); diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index 170fbafa06a..bfd5928609d 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -42,6 +42,7 @@ import { buildHelpMessage, buildVersionMessage, OptionDescriptions } from 'vs/pl import { isWindows } from 'vs/base/common/platform'; import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; class CliMain extends Disposable { @@ -91,7 +92,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, this._register(new DiskFileSystemProvider(logService))); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService())); await configurationService.initialize(); services.set(IConfigurationService, configurationService); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index a443d3bc84c..c3ae3e66423 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -70,6 +70,7 @@ import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/co import { ExtensionHostStatusService, IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService'; import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; const eventPrefix = 'monacoworkbench'; @@ -107,7 +108,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IFileService, fileService); fileService.registerProvider(Schemas.file, disposables.add(new DiskFileSystemProvider(logService))); - const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, environmentService, logService); + const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, new NullPolicyService()); services.set(IConfigurationService, configurationService); const extensionHostStatusService = new ExtensionHostStatusService(); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index b8dd198abc8..b8a397edac4 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -73,6 +73,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { DelayedLogChannel } from 'vs/workbench/services/output/common/delayedLogChannel'; import { dirname, joinPath } from 'vs/base/common/resources'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; export class BrowserMain extends Disposable { @@ -440,7 +441,7 @@ export class BrowserMain extends Disposable { private async createWorkspaceService(payload: IAnyWorkspaceIdentifier, environmentService: IWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { const configurationCache = new ConfigurationCache([Schemas.file, Schemas.vscodeUserData, Schemas.tmp] /* Cache all non native resources */, environmentService, fileService); - const workspaceService = new WorkspaceService({ remoteAuthority: this.configuration.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); + const workspaceService = new WorkspaceService({ remoteAuthority: this.configuration.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService, new NullPolicyService()); try { await workspaceService.initialize(payload); diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 98b95174864..42ada97cea0 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -50,6 +50,8 @@ import { isCI, isMacintosh } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/electron-sandbox/diskFileSystemProvider'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; +import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; export class DesktopMain extends Disposable { @@ -155,6 +157,10 @@ export class DesktopMain extends Disposable { const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId)); serviceCollection.set(IMainProcessService, mainProcessService); + // Policies + const policyService = new PolicyChannelClient(mainProcessService.getChannel('policy')); + serviceCollection.set(IPolicyService, policyService); + // Product const productService: IProductService = { _serviceBrand: undefined, ...product }; serviceCollection.set(IProductService, productService); @@ -247,7 +253,7 @@ export class DesktopMain extends Disposable { const payload = this.resolveWorkspaceInitializationPayload(environmentService); const [configurationService, storageService] = await Promise.all([ - this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, uriIdentityService, logService).then(service => { + this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, uriIdentityService, logService, policyService).then(service => { // Workspace serviceCollection.set(IWorkspaceContextService, service); @@ -325,9 +331,17 @@ export class DesktopMain extends Disposable { return workspaceInitializationPayload; } - private async createWorkspaceService(payload: IAnyWorkspaceIdentifier, environmentService: INativeWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { + private async createWorkspaceService( + payload: IAnyWorkspaceIdentifier, + environmentService: INativeWorkbenchEnvironmentService, + fileService: FileService, + remoteAgentService: IRemoteAgentService, + uriIdentityService: IUriIdentityService, + logService: ILogService, + policyService: IPolicyService + ): Promise { const configurationCache = new ConfigurationCache([Schemas.file, Schemas.vscodeUserData] /* Cache all non native resources */, environmentService, fileService); - const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); + const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService, policyService); try { await workspaceService.initialize(payload); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 0db722acbc1..b31cf92ae7c 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -40,6 +40,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { isUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; class Workspace extends BaseWorkspace { initialized: boolean = false; @@ -104,6 +105,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService, + policyService: IPolicyService ) { super(); @@ -112,7 +114,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.initRemoteUserConfigurationBarrier = new Barrier(); this.completeWorkspaceBarrier = new Barrier(); this.defaultConfiguration = this._register(new DefaultConfiguration(configurationCache, environmentService)); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, fileService, environmentService, logService)); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService)); this.configurationCache = configurationCache; this.fileService = fileService; this.uriIdentityService = uriIdentityService; diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 2fba810064f..16e6ddde07b 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -40,6 +40,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteA import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { hash } from 'vs/base/common/hash'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -107,7 +108,7 @@ suite('ConfigurationEditingService', () => { disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService)))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); await workspaceService.initialize({ id: hash(workspaceFolder.toString()).toString(16), uri: workspaceFolder diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 7f980fd1d1d..794400f725b 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -44,6 +44,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteA import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService'; import { hash } from 'vs/base/common/hash'; import { TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -77,7 +78,7 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); }); @@ -117,7 +118,7 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService())); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a')); @@ -137,7 +138,7 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService())); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); @@ -184,7 +185,7 @@ suite('WorkspaceContextService - Workspace', () => { const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null)); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -242,7 +243,7 @@ suite('WorkspaceContextService - Workspace Editing', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -485,7 +486,7 @@ suite('WorkspaceService - Initialization', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -743,7 +744,7 @@ suite('WorkspaceConfigurationService - Folder', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -1440,7 +1441,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -2101,7 +2102,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { const remoteAgentService = instantiationService.stub(IRemoteAgentService, >{ getEnvironment: () => remoteEnvironmentPromise }); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve(), needsCaching: () => false }; - testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); instantiationService.stub(IEnvironmentService, environmentService); From 0af4de3e4139fafb9e8baca6696279180b8e5499 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 16 May 2022 22:22:37 +0200 Subject: [PATCH 16/34] works! --- .../platform/policy/common/filePolicyService.ts | 8 ++++---- src/vs/platform/policy/common/policy.ts | 3 +-- src/vs/platform/policy/common/policyIpc.ts | 15 ++++++++------- .../platform/policy/node/windowsPolicyService.ts | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index c93a15b365f..1f563b4fab1 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -11,7 +11,7 @@ import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; function keysDiff(a: Map, b: Map): string[] { const result: string[] = []; @@ -29,7 +29,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; - private policies: Policies = new Map(); + private policies = new Map(); private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; @@ -56,8 +56,8 @@ export class FilePolicyService extends Disposable implements IPolicyService { await this.doRefresh(); } - private async read(): Promise { - const policies: Policies = new Map(); + private async read(): Promise> { + const policies = new Map(); try { const content = await this.fileService.readFile(this.file); diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 3d7af47c262..5071a94f3ed 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -7,8 +7,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export type PolicyName = string; -export type PolicyValue = string | boolean | number; -export type Policies = Map; +export type PolicyValue = string | boolean; export const IPolicyService = createDecorator('policy'); diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts index 00a0913f693..b3efcfa79d2 100644 --- a/src/vs/platform/policy/common/policyIpc.ts +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -6,7 +6,9 @@ import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; + +type Policies = { [name: PolicyName]: PolicyValue | undefined }; export class PolicyChannel implements IServerChannel { @@ -18,10 +20,7 @@ export class PolicyChannel implements IServerChannel { switch (event) { case 'onDidChange': return Event.map( this.service.onDidChange, - names => new Map( - names - .map(name => [name, this.service.getPolicyValue(name)]) - .filter(pair => pair[1] !== undefined) as [PolicyName, PolicyValue][]), + names => names.reduce((r, name) => ({ ...r, [name]: this.service.getPolicyValue(name) }), {}), this.disposables ); } @@ -46,14 +45,16 @@ export class PolicyChannelClient implements IPolicyService { declare readonly _serviceBrand: undefined; - private policies: Policies = new Map(); + private policies = new Map(); private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; constructor(private readonly channel: IChannel) { this.channel.listen('onDidChange')(policies => { - for (const [name, value] of policies) { + for (const name in policies) { + const value = policies[name]; + if (value === undefined) { this.policies.delete(name); } else { diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 77550a47c3b..6a324539ed4 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -5,7 +5,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; @@ -16,7 +16,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; - private policies: Policies = new Map(); + private readonly policies = new Map(); private init: Promise | undefined; private readonly _onDidChange = new Emitter(); @@ -58,7 +58,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { let first = true; - this._register(createWatcher(this.productService.win32RegValueName, policies, update => () => { + this._register(createWatcher(this.productService.win32RegValueName, policies, update => { for (const key in update) { this.policies.set(key, update[key]!); } From fe9f84127a9730f48ca4444abcaa01b27e01b99e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 16 May 2022 22:35:23 +0200 Subject: [PATCH 17/34] figure out proper init --- src/vs/platform/policy/common/filePolicyService.ts | 3 ++- src/vs/platform/policy/common/policy.ts | 7 ++++--- src/vs/platform/policy/common/policyIpc.ts | 12 ++++++++++-- src/vs/platform/policy/node/windowsPolicyService.ts | 7 ++++--- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 96974697594..f46b55cb70f 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -48,8 +48,9 @@ export class FilePolicyService extends Disposable implements IPolicyService { this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.refresh()))); } - async initialize(): Promise { + async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { await this.refresh(); + return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); } private async read(): Promise> { diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 5071a94f3ed..6446f06e9ac 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -15,14 +15,14 @@ export interface IPolicyService { readonly _serviceBrand: undefined; readonly onDidChange: Event; - initialize(): Promise; + initialize(): Promise<{ [name: PolicyName]: PolicyValue }>; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { readonly _serviceBrand: undefined; readonly onDidChange = Event.None; - async initialize() { } + async initialize() { return {}; } getPolicyValue() { return undefined; } } @@ -37,7 +37,8 @@ export class MultiPolicyService implements IPolicyService { } async initialize() { - await Promise.all(this.policyServices.map(p => p.initialize())); + const result = await Promise.all(this.policyServices.map(p => p.initialize())); + return result.reduce((r, o) => ({ ...r, ...o }), {}); } getPolicyValue(name: PolicyName) { diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts index b3efcfa79d2..c163321d085 100644 --- a/src/vs/platform/policy/common/policyIpc.ts +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -61,11 +61,19 @@ export class PolicyChannelClient implements IPolicyService { this.policies.set(name, value); } } + + this._onDidChange.fire(Object.keys(policies)); }); } - initialize(): Promise { - return this.channel.call('initialize'); + async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { + const result = await this.channel.call<{ [name: PolicyName]: PolicyValue }>('initialize'); + + for (const name in result) { + this.policies.set(name, result[name]); + } + + return result; } getPolicyValue(name: PolicyName): PolicyValue | undefined { diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 6a324539ed4..7e943f1c5ff 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -11,13 +11,14 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { createWatcher } from 'vscode-policy-watcher'; import { IStringDictionary } from 'vs/base/common/collections'; +import { Iterable } from 'vs/base/common/iterator'; export class WindowsPolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; private readonly policies = new Map(); - private init: Promise | undefined; + private init: Promise<{ [name: PolicyName]: PolicyValue }> | undefined; private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; @@ -28,7 +29,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { super(); } - initialize(): Promise { + initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { if (!this.init) { this.init = new Promise(c => { if (!this.productService.win32RegValueName) { @@ -65,7 +66,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { if (first) { first = false; - c(); + c(Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {})); } else { this._onDidChange.fire(Object.keys(update)); } From abb896879f4bb1d3e26008026308f41986743796 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Mon, 16 May 2022 14:05:05 -0700 Subject: [PATCH 18/34] Add hasPolicy tag --- .../preferences/browser/settingsEditor2.ts | 11 ++-- .../preferences/browser/settingsSearchMenu.ts | 8 ++- .../preferences/browser/settingsTree.ts | 56 ++++++++++++------- .../preferences/browser/settingsTreeModels.ts | 13 ++++- .../contrib/preferences/common/preferences.ts | 1 + 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index d2d2c183503..4bbcafeb7c2 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -43,7 +43,7 @@ import { commonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/brow import { AbstractSettingRenderer, HeightChangeParams, ISettingLinkClickEvent, ISettingOverrideClickEvent, resolveConfiguredUntrustedSettings, createTocTreeForExtensionSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree'; import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { settingsHeaderBorder, settingsSashBorder, settingsTextInputBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IOpenSettingsOptions, IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingMatchType, SettingValueType, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; @@ -126,6 +126,7 @@ export class SettingsEditor2 extends EditorPane { `@${FEATURE_SETTING_TAG}remote`, `@${FEATURE_SETTING_TAG}timeline`, `@${FEATURE_SETTING_TAG}notebook`, + `@${POLICY_SETTING_TAG}` ]; private static shouldSettingUpdateFast(type: SettingValueType | SettingValueType[]): boolean { @@ -866,10 +867,10 @@ export class SettingsEditor2 extends EditorPane { // the element was not found } })); - this._register(this.settingRenderers.onApplyLanguageFilter((lang: string) => { - if (this.searchWidget) { - // Prepend the language filter to the query. - const newQuery = `@${LANGUAGE_SETTING_TAG}${lang} ${this.searchWidget.getValue().trimStart()}`; + this._register(this.settingRenderers.onApplyFilter((filter: string) => { + if (this.searchWidget && !this.searchWidget.getValue().includes(filter)) { + // Prepend the filter to the query. + const newQuery = `${filter} ${this.searchWidget.getValue().trimStart()}`; this.focusSearch(newQuery, false); } })); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts index e39d23eb58e..9a1c0ec289c 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts @@ -10,7 +10,7 @@ import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestCont import { localize } from 'vs/nls'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; -import { EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, GENERAL_TAG_SETTING_TAG, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, GENERAL_TAG_SETTING_TAG, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenuActionViewItem { private readonly suggestController: SuggestController | null; @@ -135,6 +135,12 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu localize('onlineSettingsSearch', "Online services"), localize('onlineSettingsSearchTooltip', "Show settings for online services"), '@tag:usesOnlineServices' + ), + this.createToggleAction( + 'policySettingsSearch', + localize('policySettingsSearch', "Policy services"), + localize('policySettingsSearchTooltip', "Show settings for policy services"), + `@${POLICY_SETTING_TAG}` ) ]; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 673de4bf5aa..2e2ee4e7257 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -43,7 +43,7 @@ import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerg import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { inspectSetting, ISettingsEditorViewState, settingKeyToDisplayFormat, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { ExcludeSettingWidget, ISettingListChangeEvent, IListDataItem, ListSettingWidget, ObjectSettingDropdownWidget, IObjectDataItem, IObjectEnumOption, ObjectValue, IObjectValueSuggester, IObjectKeySuggester, ObjectSettingCheckboxWidget } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; -import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; +import { LANGUAGE_SETTING_TAG, POLICY_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { getDefaultIgnoredSettings, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; @@ -740,8 +740,8 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre protected readonly _onDidChangeSettingHeight = this._register(new Emitter()); readonly onDidChangeSettingHeight: Event = this._onDidChangeSettingHeight.event; - protected readonly _onApplyLanguageFilter = this._register(new Emitter()); - readonly onApplyLanguageFilter: Event = this._onApplyLanguageFilter.event; + protected readonly _onApplyFilter = this._register(new Emitter()); + readonly onApplyFilter: Event = this._onApplyFilter.event; private readonly markdownRenderer: MarkdownRenderer; @@ -794,14 +794,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); const toDispose = new DisposableStore(); - - const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); - const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); - toDispose.add(attachStylerCallback(this._themeService, { editorInfoForeground }, colors => { - policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorInfoForeground?.toString() || ''); - })); - const element = DOM.append(policyWarningElement, $('span')); - element.textContent = localize('policyLabel', "This setting is managed by your organization."); + const policyWarningElement = this.renderPolicyLabel(container, toDispose); const toolbarContainer = DOM.append(container, $('.setting-toolbar-container')); const toolbar = this.renderSettingToolbar(toolbarContainer); @@ -848,6 +841,33 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre }); } + protected renderPolicyLabel(container: HTMLElement, toDispose: DisposableStore): HTMLElement { + const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); + const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); + toDispose.add(attachStylerCallback(this._themeService, { editorInfoForeground }, colors => { + policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorInfoForeground?.toString() || ''); + })); + const element = DOM.append(policyWarningElement, $('span')); + element.textContent = localize('policyLabel', "This setting is managed by your organization."); + const viewPolicyLabel = localize('viewPolicySettings', "View policy settings"); + const linkElement: HTMLAnchorElement = DOM.append(policyWarningElement, $('a')); + linkElement.textContent = viewPolicyLabel; + linkElement.setAttribute('tabindex', '0'); + linkElement.href = '#'; + toDispose.add(DOM.addStandardDisposableListener(linkElement, DOM.EventType.CLICK, (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + this._onApplyFilter.fire(`@${POLICY_SETTING_TAG}`); + })); + toDispose.add(DOM.addStandardDisposableListener(linkElement, DOM.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { + e.stopPropagation(); + this._onApplyFilter.fire(`@${POLICY_SETTING_TAG}`); + } + })); + return policyWarningElement; + } + protected renderSettingToolbar(container: HTMLElement): ToolBar { const toggleMenuKeybinding = this._keybindingService.lookupKeybinding(SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU); let toggleMenuTitle = localize('settingsContextMenuTitle', "More Actions... "); @@ -1098,7 +1118,7 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I template.elementDisposables.add(template.button.onDidClick(() => { if (isLanguageTagSetting) { - this._onApplyLanguageFilter.fire(plainKey); + this._onApplyFilter.fire(`@${LANGUAGE_SETTING_TAG}:${plainKey}`); } else { this._onDidOpenSettings.fire(dataElement.setting.key); } @@ -1836,13 +1856,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const toolbar = this.renderSettingToolbar(toolbarContainer); toDispose.add(toolbar); - const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); - const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); - toDispose.add(attachStylerCallback(this._themeService, { editorInfoForeground }, colors => { - policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorInfoForeground?.toString() || ''); - })); - const element = DOM.append(policyWarningElement, $('span')); - element.textContent = localize('policyLabel', "This setting is managed by your organization."); + const policyWarningElement = this.renderPolicyLabel(container, toDispose); const template: ISettingBoolItemTemplate = { toDispose, @@ -1943,7 +1957,7 @@ export class SettingTreeRenderers { readonly onDidChangeSettingHeight: Event; - readonly onApplyLanguageFilter: Event; + readonly onApplyFilter: Event; readonly allRenderers: ITreeRenderer[]; @@ -1992,7 +2006,7 @@ export class SettingTreeRenderers { this.onDidClickSettingLink = Event.any(...settingRenderers.map(r => r.onDidClickSettingLink)); this.onDidFocusSetting = Event.any(...settingRenderers.map(r => r.onDidFocusSetting)); this.onDidChangeSettingHeight = Event.any(...settingRenderers.map(r => r.onDidChangeSettingHeight)); - this.onApplyLanguageFilter = Event.any(...settingRenderers.map(r => r.onApplyLanguageFilter)); + this.onApplyFilter = Event.any(...settingRenderers.map(r => r.onApplyFilter)); this.allRenderers = [ ...settingRenderers, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 6e0c5b34b43..b2f6eac0cdc 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -11,7 +11,7 @@ import { localize } from 'vs/nls'; import { ConfigurationTarget, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { ITOCEntry, knownAcronyms, knownTermMappings, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; -import { ENABLE_LANGUAGE_FILTER, MODIFIED_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { ENABLE_LANGUAGE_FILTER, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; @@ -238,7 +238,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { this.value = displayValue; this.isConfigured = isConfigured; - if (isConfigured || this.setting.tags || this.tags || this.setting.restricted) { + if (isConfigured || this.setting.tags || this.tags || this.setting.restricted || this.hasPolicyValue) { // Don't create an empty Set for all 1000 settings, only if needed this.tags = new Set(); if (isConfigured) { @@ -252,6 +252,10 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { if (this.setting.restricted) { this.tags.add(REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG); } + + if (this.hasPolicyValue) { + this.tags.add(POLICY_SETTING_TAG); + } } this.overriddenScopeList = overriddenScopeList; @@ -905,6 +909,11 @@ export function parseQuery(query: string): IParsedQuery { return ''; }); + query = query.replace(`@${POLICY_SETTING_TAG}`, () => { + tags.push(POLICY_SETTING_TAG); + return ''; + }); + const extensions: string[] = []; const features: string[] = []; const ids: string[] = []; diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index 6138098d36f..6b87d1ae46b 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -78,6 +78,7 @@ export const FEATURE_SETTING_TAG = 'feature:'; export const ID_SETTING_TAG = 'id:'; export const LANGUAGE_SETTING_TAG = 'lang:'; export const GENERAL_TAG_SETTING_TAG = 'tag:'; +export const POLICY_SETTING_TAG = 'hasPolicy'; export const WORKSPACE_TRUST_SETTING_TAG = 'workspaceTrust'; export const REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG = 'requireTrustedWorkspace'; export const KEYBOARD_LAYOUT_OPEN_PICKER = 'workbench.action.openKeyboardLayoutPicker'; From 558b5fbc2716b1c2339a83e95450c1972eb18ddf Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 17 May 2022 08:48:08 +0200 Subject: [PATCH 19/34] fix policy ipc serialization --- src/vs/platform/policy/common/policyIpc.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts index c163321d085..2fdb1e62cb1 100644 --- a/src/vs/platform/policy/common/policyIpc.ts +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -8,8 +8,6 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; -type Policies = { [name: PolicyName]: PolicyValue | undefined }; - export class PolicyChannel implements IServerChannel { private readonly disposables = new DisposableStore(); @@ -20,7 +18,7 @@ export class PolicyChannel implements IServerChannel { switch (event) { case 'onDidChange': return Event.map( this.service.onDidChange, - names => names.reduce((r, name) => ({ ...r, [name]: this.service.getPolicyValue(name) }), {}), + names => names.reduce((r, name) => ({ ...r, [name]: this.service.getPolicyValue(name) ?? null }), {}), this.disposables ); } @@ -51,11 +49,11 @@ export class PolicyChannelClient implements IPolicyService { readonly onDidChange: Event = this._onDidChange.event; constructor(private readonly channel: IChannel) { - this.channel.listen('onDidChange')(policies => { + this.channel.listen('onDidChange')(policies => { for (const name in policies) { - const value = policies[name]; + const value = policies[name as keyof typeof policies]; - if (value === undefined) { + if (value === null) { this.policies.delete(name); } else { this.policies.set(name, value); From a1090d51c50e6e266e28b69f00dac615e380bc67 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 17 May 2022 08:55:41 +0200 Subject: [PATCH 20/34] make sure policy init always returns the right values --- src/vs/platform/policy/node/windowsPolicyService.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 7e943f1c5ff..9b9cba059a9 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -18,7 +18,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; private readonly policies = new Map(); - private init: Promise<{ [name: PolicyName]: PolicyValue }> | undefined; + private init: Promise | undefined; private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; @@ -29,7 +29,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { super(); } - initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { + async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { if (!this.init) { this.init = new Promise(c => { if (!this.productService.win32RegValueName) { @@ -66,7 +66,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { if (first) { first = false; - c(Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {})); + c(); } else { this._onDidChange.fire(Object.keys(update)); } @@ -74,7 +74,8 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { }); } - return this.init; + await this.init; + return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); } getPolicyValue(name: PolicyName): PolicyValue | undefined { From 2f19fc5c580b56c4f1e40215432df4c682b3d8a5 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 17 May 2022 09:00:45 +0200 Subject: [PATCH 21/34] delete policy removals --- src/vs/platform/policy/node/windowsPolicyService.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 9b9cba059a9..36a083dd2d7 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -61,7 +61,13 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { this._register(createWatcher(this.productService.win32RegValueName, policies, update => { for (const key in update) { - this.policies.set(key, update[key]!); + const value = update[key] as any; + + if (value === undefined) { + this.policies.delete(key); + } else { + this.policies.set(key, value); + } } if (first) { From 2d1940e1ab04c5b679409f434272c2752d7ce1f5 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 17 May 2022 09:33:32 +0200 Subject: [PATCH 22/34] wip: registerPolicyDefinitions --- src/vs/code/electron-main/main.ts | 4 +- src/vs/code/node/cliProcessMain.ts | 2 +- .../configuration/common/configurations.ts | 32 +++++++++- .../policy/common/filePolicyService.ts | 6 +- src/vs/platform/policy/common/policy.ts | 34 ++--------- src/vs/platform/policy/common/policyIpc.ts | 11 ++-- .../policy/node/windowsPolicyService.ts | 60 +++++++------------ 7 files changed, 66 insertions(+), 83 deletions(-) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index f31b96ebece..16a7a727c15 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -169,7 +169,7 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Policy - const policyService = isWindows ? new WindowsPolicyService(productService) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration @@ -232,8 +232,6 @@ class CodeMain { environmentMainService.backupHome ].map(path => path ? FSPromises.mkdir(path, { recursive: true }) : undefined)), - policyService.initialize(), - // Configuration service configurationService.initialize(), diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 2217a4b70b1..4ea5092c119 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -132,7 +132,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // Policy - const policyService = isWindows ? new WindowsPolicyService(productService) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 1834735dccf..aed37d85e47 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -11,7 +11,7 @@ import { equals } from 'vs/base/common/objects'; import { addToValueTree, IOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; -import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export class DefaultConfiguration extends Disposable { @@ -91,10 +91,38 @@ export class PolicyConfiguration extends Disposable { super(); } + // TODO@sandy: make nice + private getPolicyDefinitions(): IStringDictionary { + const result: IStringDictionary = {}; + const configRegistry = Registry.as(Extensions.Configuration); + + for (const configuration of configRegistry.getConfigurations()) { + if (configuration.properties) { + for (const key in configuration.properties) { + const config = configuration.properties[key]; + const policy = config.policy; + + if (policy) { + if (config.type !== 'string' && config.type !== 'number') { + console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); + continue; + } + + result[policy.name] = { type: config.type }; + } + } + } + } + + return result; + } + async initialize(): Promise { - await this.policyService.initialize(); + // TODO@sandy: go through registry + await this.policyService.registerPolicyDefinitions(this.getPolicyDefinitions()); this.update(this.defaultConfiguration.configurationModel.keys, false); this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); + // TODO@sandy: also make sure that policy configurations that are registered after initialize() also call registerPolicyDefinitions() this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.update(properties, true))); return this._configurationModel; } diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index f46b55cb70f..f3786b547e8 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ThrottledDelayer } from 'vs/base/common/async'; +import { IStringDictionary } from 'vs/base/common/collections'; import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -11,7 +12,7 @@ import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; function keysDiff(a: Map, b: Map): string[] { const result: string[] = []; @@ -48,7 +49,8 @@ export class FilePolicyService extends Disposable implements IPolicyService { this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.refresh()))); } - async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { + // TODO@sandeep respect only registered policy definitions + async registerPolicyDefinitions(policies: IStringDictionary): Promise> { await this.refresh(); return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); } diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 6446f06e9ac..21ef3302bb0 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export type PolicyName = string; export type PolicyValue = string | boolean; +export type PolicyDefinition = { type: 'string' | 'number' }; export const IPolicyService = createDecorator('policy'); @@ -15,41 +17,13 @@ export interface IPolicyService { readonly _serviceBrand: undefined; readonly onDidChange: Event; - initialize(): Promise<{ [name: PolicyName]: PolicyValue }>; + registerPolicyDefinitions(policies: IStringDictionary): Promise>; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { readonly _serviceBrand: undefined; readonly onDidChange = Event.None; - async initialize() { return {}; } + async registerPolicyDefinitions() { return {}; } getPolicyValue() { return undefined; } } - -export class MultiPolicyService implements IPolicyService { - - readonly _serviceBrand: undefined; - - readonly onDidChange: Event; - - constructor(private policyServices: readonly IPolicyService[]) { - this.onDidChange = Event.any(...policyServices.map(p => p.onDidChange)); - } - - async initialize() { - const result = await Promise.all(this.policyServices.map(p => p.initialize())); - return result.reduce((r, o) => ({ ...r, ...o }), {}); - } - - getPolicyValue(name: PolicyName) { - for (const policyService of this.policyServices) { - const result = policyService.getPolicyValue(name); - - if (typeof result !== 'undefined') { - return result; - } - } - - return undefined; - } -} diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts index 2fdb1e62cb1..177b1d51ae6 100644 --- a/src/vs/platform/policy/common/policyIpc.ts +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IStringDictionary } from 'vs/base/common/collections'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; export class PolicyChannel implements IServerChannel { @@ -26,9 +27,9 @@ export class PolicyChannel implements IServerChannel { throw new Error(`Event not found: ${event}`); } - call(_: unknown, command: string): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { - case 'initialize': return this.service.initialize(); + case 'initialize': return this.service.registerPolicyDefinitions(arg as IStringDictionary); } throw new Error(`Call not found: ${command}`); @@ -64,8 +65,8 @@ export class PolicyChannelClient implements IPolicyService { }); } - async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { - const result = await this.channel.call<{ [name: PolicyName]: PolicyValue }>('initialize'); + async registerPolicyDefinitions(policies: IStringDictionary): Promise> { + const result = await this.channel.call<{ [name: PolicyName]: PolicyValue }>('initialize', policies); for (const name in result) { this.policies.set(name, result[name]); diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 36a083dd2d7..23439893dc8 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -3,13 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { createWatcher } from 'vscode-policy-watcher'; +import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { createWatcher, Watcher } from 'vscode-policy-watcher'; import { IStringDictionary } from 'vs/base/common/collections'; import { Iterable } from 'vs/base/common/iterator'; @@ -18,48 +15,21 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; private readonly policies = new Map(); - private init: Promise | undefined; + private init: Promise | undefined; private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; - constructor( - @IProductService private readonly productService: IProductService - ) { + constructor(private readonly productName: string) { super(); } - async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { + async registerPolicyDefinitions(policies: IStringDictionary): Promise> { if (!this.init) { this.init = new Promise(c => { - if (!this.productService.win32RegValueName) { - return; - } - - const policies: IStringDictionary<{ type: 'string' | 'number' }> = {}; - const configRegistry = Registry.as(Extensions.Configuration); - - for (const configuration of configRegistry.getConfigurations()) { - if (configuration.properties) { - for (const key in configuration.properties) { - const config = configuration.properties[key]; - const policy = config.policy; - - if (policy) { - if (config.type !== 'string' && config.type !== 'number') { - console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); - continue; - } - - policies[policy.name] = { type: config.type }; - } - } - } - } - let first = true; - this._register(createWatcher(this.productService.win32RegValueName, policies, update => { + const watcher = createWatcher(this.productName, policies, update => { for (const key in update) { const value = update[key] as any; @@ -72,15 +42,25 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { if (first) { first = false; - c(); + c(watcher); } else { this._onDidChange.fire(Object.keys(update)); } - })); + }); + + this._register(watcher); }); + + await this.init; + } else { + const watcher = await this.init; + const promise = Event.toPromise(this.onDidChange); + watcher.addPolicies(policies); + await promise; } - await this.init; + // TODO@joao: heavy cleanup + return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); } From 5db383efe60fd80328658a9625d39b25de8ebafe Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 17 May 2022 09:35:35 +0200 Subject: [PATCH 23/34] use FilePolicyService --- src/vs/code/electron-main/main.ts | 3 ++- src/vs/code/node/cliProcessMain.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 16a7a727c15..029a5c98f37 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -64,6 +64,7 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; /** * The main VS Code entry point. @@ -169,7 +170,7 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Policy - const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : environmentMainService.policyFile ? new FilePolicyService(environmentMainService.policyFile, fileService, logService) : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 4ea5092c119..184289c296e 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -40,6 +40,7 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ConsoleLogger, getLogLevel, ILogger, ILogService, LogLevel, MultiplexLogService } from 'vs/platform/log/common/log'; import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; import product from 'vs/platform/product/common/product'; @@ -132,7 +133,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // Policy - const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration From 562c9f6d8b7d1aac3a49aebb6e26fbeabe55dcc0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 16:01:10 +0200 Subject: [PATCH 24/34] register policy definitions from policy configuration --- .../sharedProcess/sharedProcessMain.ts | 2 +- src/vs/code/electron-main/main.ts | 6 +- src/vs/code/node/cliProcessMain.ts | 2 +- .../common/configurationService.ts | 4 +- .../configuration/common/configurations.ts | 56 +++++++++---------- .../test/common/configurationService.test.ts | 22 ++++---- .../test/common/policyConfiguration.test.ts | 2 +- .../policy/common/filePolicyService.ts | 17 +++++- .../test/common/userDataSyncClient.ts | 2 +- .../node/remoteExtensionHostAgentCli.ts | 2 +- src/vs/server/node/serverServices.ts | 2 +- .../browser/configurationService.ts | 2 +- 12 files changed, 66 insertions(+), 53 deletions(-) diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 6b07e7c7ddd..8a2cfbc36dc 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -228,7 +228,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService, logService)); services.set(IConfigurationService, configurationService); // Storage (global access only) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 029a5c98f37..d1563aebe63 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -170,11 +170,13 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Policy - const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : environmentMainService.policyFile ? new FilePolicyService(environmentMainService.policyFile, fileService, logService) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) + : environmentMainService.policyFile ? new FilePolicyService(environmentMainService.policyFile, fileService, logService) + : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration - const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, policyService); + const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, policyService, logService); services.set(IConfigurationService, configurationService); // Lifecycle diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 184289c296e..f0151458057 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -137,7 +137,7 @@ class CliMain extends Disposable { services.set(IPolicyService, policyService); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService, logService)); services.set(IConfigurationService, configurationService); // Init config diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 3d0422d38c1..a1bd9906ee0 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -12,6 +12,7 @@ import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, I import { Configuration, ConfigurationChangeEvent, ConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; import { IPolicyService } from 'vs/platform/policy/common/policy'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { @@ -31,10 +32,11 @@ export class ConfigurationService extends Disposable implements IConfigurationSe private readonly settingsResource: URI, fileService: IFileService, policyService: IPolicyService, + logService: ILogService, ) { super(); this.defaultConfiguration = this._register(new DefaultConfiguration()); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService)); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel()); diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index aed37d85e47..9cd8e84917b 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -8,9 +8,11 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; +import { isEmptyObject } from 'vs/base/common/types'; import { addToValueTree, IOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; +import { ILogService } from 'vs/platform/log/common/log'; import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -86,45 +88,41 @@ export class PolicyConfiguration extends Disposable { constructor( private readonly defaultConfiguration: DefaultConfiguration, - @IPolicyService private readonly policyService: IPolicyService + @IPolicyService private readonly policyService: IPolicyService, + @ILogService private readonly logService: ILogService ) { super(); } - // TODO@sandy: make nice - private getPolicyDefinitions(): IStringDictionary { - const result: IStringDictionary = {}; - const configRegistry = Registry.as(Extensions.Configuration); + async initialize(): Promise { + await this.registerPolicyDefinitionsAndUpdate(this.defaultConfiguration.configurationModel.keys, false); + this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.registerPolicyDefinitionsAndUpdate(properties, true))); + return this._configurationModel; + } - for (const configuration of configRegistry.getConfigurations()) { - if (configuration.properties) { - for (const key in configuration.properties) { - const config = configuration.properties[key]; - const policy = config.policy; + private async registerPolicyDefinitionsAndUpdate(properties: string[], trigger: boolean): Promise { + const policyDefinitions: IStringDictionary = {}; + const keys: string[] = []; + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - if (policy) { - if (config.type !== 'string' && config.type !== 'number') { - console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); - continue; - } - - result[policy.name] = { type: config.type }; - } + for (const key of properties) { + const config = configurationProperties[key]; + const policy = config.policy; + if (policy) { + if (config.type !== 'string' && config.type !== 'number') { + this.logService.warn(`Policy ${policy.name} has unsupported type ${config.type}`); + continue; } + keys.push(key); + policyDefinitions[policy.name] = { type: config.type }; } } - return result; - } - - async initialize(): Promise { - // TODO@sandy: go through registry - await this.policyService.registerPolicyDefinitions(this.getPolicyDefinitions()); - this.update(this.defaultConfiguration.configurationModel.keys, false); - this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); - // TODO@sandy: also make sure that policy configurations that are registered after initialize() also call registerPolicyDefinitions() - this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.update(properties, true))); - return this._configurationModel; + if (!isEmptyObject(policyDefinitions)) { + await this.policyService.registerPolicyDefinitions(policyDefinitions); + this.update(keys, trigger); + } } private onDidChangePolicies(policyNames: readonly PolicyName[]): void { diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index d49af5f41f6..3b1155bf20a 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -37,7 +37,7 @@ suite('ConfigurationService', () => { test('simple', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -50,7 +50,7 @@ suite('ConfigurationService', () => { test('config gets flattened', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ testworkbench: { @@ -69,7 +69,7 @@ suite('ConfigurationService', () => { test('error case does not explode', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString(',,,,')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -79,7 +79,7 @@ suite('ConfigurationService', () => { }); test('missing file does not explode', async () => { - const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string }>(); @@ -88,7 +88,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file does not exist', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); return new Promise((c, e) => { disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => { @@ -101,7 +101,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file exists', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); await testObject.initialize(); @@ -117,7 +117,7 @@ suite('ConfigurationService', () => { test('reloadConfiguration', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); let config = testObject.getValue<{ foo: string; @@ -156,7 +156,7 @@ suite('ConfigurationService', () => { } }); - let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService())); + let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); let setting = testObject.getValue(); @@ -164,7 +164,7 @@ suite('ConfigurationService', () => { assert.strictEqual(setting.configuration.service.testSetting, 'isSet'); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); setting = testObject.getValue(); @@ -192,7 +192,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); testObject.initialize(); let res = testObject.inspect('something.missing'); @@ -227,7 +227,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); testObject.initialize(); let res = testObject.inspect('lookup.service.testNullSetting'); diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index 784334ffa7c..116322e570d 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -65,7 +65,7 @@ suite('PolicyConfiguration', () => { const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); fileService.registerProvider(policyFile.scheme, diskFileSystemProvider); policyService = new FilePolicyService(policyFile, fileService, new NullLogService()); - testObject = disposables.add(new PolicyConfiguration(defaultConfiguration, policyService)); + testObject = disposables.add(new PolicyConfiguration(defaultConfiguration, policyService, new NullLogService())); }); teardown(() => disposables.clear()); diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index f3786b547e8..4dadb9a1716 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -30,6 +30,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; + private readonly policyNames: Set = new Set(); private policies = new Map(); private readonly _onDidChange = new Emitter(); @@ -49,9 +50,17 @@ export class FilePolicyService extends Disposable implements IPolicyService { this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.refresh()))); } - // TODO@sandeep respect only registered policy definitions async registerPolicyDefinitions(policies: IStringDictionary): Promise> { - await this.refresh(); + let hasNewPolicies = false; + for (const key of Object.keys(policies)) { + if (!this.policyNames.has(key)) { + hasNewPolicies = true; + this.policyNames.add(key); + } + } + if (hasNewPolicies) { + await this.refresh(); + } return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); } @@ -67,7 +76,9 @@ export class FilePolicyService extends Disposable implements IPolicyService { } for (const key of Object.keys(raw)) { - policies.set(key, raw[key]); + if (this.policyNames.has(key)) { + policies.set(key, raw[key]); + } } } catch (error) { if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 2d888a524c9..12abf923f28 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -87,7 +87,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IStorageService, this._register(new InMemoryStorageService())); - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService())); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService(), logService)); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); this.instantiationService.stub(IUriIdentityService, this.instantiationService.createInstance(UriIdentityService)); diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index bfd5928609d..36c01538db4 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -92,7 +92,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, this._register(new DiskFileSystemProvider(logService))); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService())); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService(), logService)); await configurationService.initialize(); services.set(IConfigurationService, configurationService); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index c3ae3e66423..9885e9a1982 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -108,7 +108,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IFileService, fileService); fileService.registerProvider(Schemas.file, disposables.add(new DiskFileSystemProvider(logService))); - const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, new NullPolicyService()); + const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, new NullPolicyService(), logService); services.set(IConfigurationService, configurationService); const extensionHostStatusService = new ExtensionHostStatusService(); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index c4e251483be..26f97a95502 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -114,7 +114,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.initRemoteUserConfigurationBarrier = new Barrier(); this.completeWorkspaceBarrier = new Barrier(); this.defaultConfiguration = this._register(new DefaultConfiguration(configurationCache, environmentService)); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService)); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.configurationCache = configurationCache; this.fileService = fileService; this.uriIdentityService = uriIdentityService; From 3f092b812dbcababef496899f83a52bbdf014e88 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 16:29:25 +0200 Subject: [PATCH 25/34] fix tests --- .../configuration/common/configurations.ts | 15 +++++-- .../test/common/policyConfiguration.test.ts | 44 +++++++++---------- .../configurationEditingService.test.ts | 4 +- .../test/browser/configurationService.test.ts | 3 +- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 9cd8e84917b..1fc471bb739 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -108,19 +108,26 @@ export class PolicyConfiguration extends Disposable { for (const key of properties) { const config = configurationProperties[key]; - const policy = config.policy; - if (policy) { + if (!config) { + // Config is removed. So add it to the list if in case it was registered as policy before + keys.push(key); + continue; + } + if (config.policy) { if (config.type !== 'string' && config.type !== 'number') { - this.logService.warn(`Policy ${policy.name} has unsupported type ${config.type}`); + this.logService.warn(`Policy ${config.policy.name} has unsupported type ${config.type}`); continue; } keys.push(key); - policyDefinitions[policy.name] = { type: config.type }; + policyDefinitions[config.policy.name] = { type: config.type }; } } if (!isEmptyObject(policyDefinitions)) { await this.policyService.registerPolicyDefinitions(policyDefinitions); + } + + if (keys.length) { this.update(keys, trigger); } } diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index 116322e570d..ba2aa5d527e 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -33,16 +33,16 @@ suite('PolicyConfiguration', () => { 'type': 'object', 'properties': { 'policy.settingA': { - 'type': 'boolean', - 'default': true, + 'type': 'string', + 'default': 'defaultValueA', policy: { name: 'PolicySettingA', minimumVersion: '1.0.0', } }, 'policy.settingB': { - 'type': 'boolean', - 'default': true, + 'type': 'string', + 'default': 'defaultValueB', policy: { name: 'PolicySettingB', minimumVersion: '1.0.0', @@ -71,12 +71,12 @@ suite('PolicyConfiguration', () => { teardown(() => disposables.clear()); test('initialize: with policies', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueA'); assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA']); @@ -95,44 +95,44 @@ suite('PolicyConfiguration', () => { }); test('initialize: with policies but not registered', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false, 'PolicySettingC': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA', 'PolicySettingB': 'policyValueB', 'PolicySettingC': 'policyValueC' }))); await testObject.initialize(); const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingA'), false); - assert.strictEqual(acutal.getValue('policy.settingB'), false); + assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueA'); + assert.strictEqual(acutal.getValue('policy.settingB'), 'policyValueB'); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA', 'policy.settingB']); assert.deepStrictEqual(acutal.overrides, []); }); test('change: when policy is added', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false, 'PolicySettingC': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA', 'PolicySettingB': 'policyValueB', 'PolicySettingC': 'policyValueC' }))); await promise; const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingA'), false); - assert.strictEqual(acutal.getValue('policy.settingB'), false); + assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueA'); + assert.strictEqual(acutal.getValue('policy.settingB'), 'policyValueB'); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA', 'policy.settingB']); assert.deepStrictEqual(acutal.overrides, []); }); test('change: when policy is updated', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': true }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueAChanged' }))); await promise; const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingA'), true); + assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueAChanged'); assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA']); @@ -140,7 +140,7 @@ suite('PolicyConfiguration', () => { }); test('change: when policy is removed', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); @@ -156,13 +156,13 @@ suite('PolicyConfiguration', () => { }); test('change: when policy setting is registered', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingC': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingC': 'policyValueC' }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); policyConfigurationNode.properties!['policy.settingC'] = { - 'type': 'boolean', - 'default': true, + 'type': 'string', + 'default': 'defaultValueC', policy: { name: 'PolicySettingC', minimumVersion: '1.0.0', @@ -172,7 +172,7 @@ suite('PolicyConfiguration', () => { await promise; const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingC'), false); + assert.strictEqual(acutal.getValue('policy.settingC'), 'policyValueC'); assert.strictEqual(acutal.getValue('policy.settingA'), undefined); assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); @@ -181,7 +181,7 @@ suite('PolicyConfiguration', () => { }); test('change: when policy setting is deregistered', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 47d3e6b8706..727e4328c1f 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -41,7 +41,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteA import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { hash } from 'vs/base/common/hash'; -import { NullPolicyService } from 'vs/platform/policy/common/policy'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -109,7 +109,7 @@ suite('ConfigurationEditingService', () => { disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService)))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); await workspaceService.initialize({ id: hash(workspaceFolder.toString()).toString(16), uri: workspaceFolder diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index b623dcdb5fc..9cc61d7deca 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -45,6 +45,7 @@ import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remot import { hash } from 'vs/base/common/hash'; import { TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -744,7 +745,7 @@ suite('WorkspaceConfigurationService - Folder', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); From 94a16a85aae7c2c9ad94cbd9df03d058e565b17b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 17:01:21 +0200 Subject: [PATCH 26/34] - faster unit tests use faketimer - little refactoring --- .../configuration/common/configurations.ts | 10 +++----- .../test/common/policyConfiguration.test.ts | 25 ++++++++++++------- .../configurationEditingService.test.ts | 9 ++++--- .../test/browser/configurationService.test.ts | 9 ++++--- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 1fc471bb739..6099234ef8e 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -95,13 +95,13 @@ export class PolicyConfiguration extends Disposable { } async initialize(): Promise { - await this.registerPolicyDefinitionsAndUpdate(this.defaultConfiguration.configurationModel.keys, false); + this.update(await this.registerPolicyDefinitions(this.defaultConfiguration.configurationModel.keys), false); this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); - this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.registerPolicyDefinitionsAndUpdate(properties, true))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(async ({ properties }) => this.update(await this.registerPolicyDefinitions(properties), true))); return this._configurationModel; } - private async registerPolicyDefinitionsAndUpdate(properties: string[], trigger: boolean): Promise { + private async registerPolicyDefinitions(properties: string[]): Promise { const policyDefinitions: IStringDictionary = {}; const keys: string[] = []; const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); @@ -127,9 +127,7 @@ export class PolicyConfiguration extends Disposable { await this.policyService.registerPolicyDefinitions(policyDefinitions); } - if (keys.length) { - this.update(keys, trigger); - } + return keys; } private onDidChangePolicies(policyNames: readonly PolicyName[]): void { diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index ba2aa5d527e..d5b31e5516d 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -18,6 +18,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { deepClone } from 'vs/base/common/objects'; import { IPolicyService } from 'vs/platform/policy/common/policy'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; suite('PolicyConfiguration', () => { @@ -111,9 +112,11 @@ suite('PolicyConfiguration', () => { await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA', 'PolicySettingB': 'policyValueB', 'PolicySettingC': 'policyValueC' }))); - await promise; + await runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA', 'PolicySettingB': 'policyValueB', 'PolicySettingC': 'policyValueC' }))); + await promise; + }); const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueA'); @@ -127,9 +130,11 @@ suite('PolicyConfiguration', () => { await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueAChanged' }))); - await promise; + await runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueAChanged' }))); + await promise; + }); const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueAChanged'); @@ -143,9 +148,11 @@ suite('PolicyConfiguration', () => { await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({}))); - await promise; + await runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({}))); + await promise; + }); const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), undefined); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 727e4328c1f..995442cd8b8 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -42,6 +42,7 @@ import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/worksp import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { hash } from 'vs/base/common/hash'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -197,9 +198,11 @@ suite('ConfigurationEditingService', () => { }); test('errors cases - ERROR_POLICY_CONFIGURATION', async () => { - const promise = Event.toPromise(instantiationService.get(IConfigurationService).onDidChangeConfiguration); - await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationEditing.service.policySetting": "policyValue" }')); - await promise; + await runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(instantiationService.get(IConfigurationService).onDidChangeConfiguration); + await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationEditing.service.policySetting": "policyValue" }')); + await promise; + }); try { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.policySetting', value: 'value' }, { donotNotifyError: true }); } catch (error) { diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 9cc61d7deca..d36be57c247 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -46,6 +46,7 @@ import { hash } from 'vs/base/common/hash'; import { TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -968,9 +969,11 @@ suite('WorkspaceConfigurationService - Folder', () => { }); test('policy value override all', async () => { - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); - const result = await promise; + const result = await runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); + return promise; + }); assert.deepStrictEqual(result.affectedKeys, ['configurationService.folder.policySetting']); assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'policyValue'); assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, 'policyValue'); From c88474c6bc775ed68eb2448279ca0a3a79d016ea Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 18 May 2022 11:03:34 +0200 Subject: [PATCH 27/34] depend on vscode-policy-watcher --- package.json | 1 + yarn.lock | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/package.json b/package.json index affa8ac0cdc..ed114a6ceac 100644 --- a/package.json +++ b/package.json @@ -221,6 +221,7 @@ }, "optionalDependencies": { "@vscode/windows-registry": "1.0.6", + "vscode-policy-watcher": "^1.0.0", "windows-foreground-love": "0.4.0", "windows-mutex": "0.4.1", "windows-process-tree": "0.3.3" diff --git a/yarn.lock b/yarn.lock index 13805339355..ac4091e65aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8136,6 +8136,11 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" +node-addon-api@*: + version "5.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" + integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== + node-addon-api@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" @@ -11791,6 +11796,14 @@ vscode-oniguruma@1.6.1: resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz#2bf4dfcfe3dd2e56eb549a3068c8ee39e6c30ce5" integrity sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ== +vscode-policy-watcher@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/vscode-policy-watcher/-/vscode-policy-watcher-1.0.0.tgz#08138a5ab15a1d6c021df51716a4daa97d74b471" + integrity sha512-j9nWBInu9wPW+3Uw/flYXP0kTewTqTfM6ADC24stqxURCAGOWxYz3ArBo21o+3zOrxpfCjc5mCbUNfLk+6IHuA== + dependencies: + bindings "^1.5.0" + node-addon-api "*" + vscode-proxy-agent@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.12.0.tgz#0775f464b9519b0c903da4dcf50851e1453f4e48" From e2029b6cbed68372b14856222ea7aaf5fb61fe5f Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 18 May 2022 11:15:18 +0200 Subject: [PATCH 28/34] cleanup vscode-policy-watcher --- build/.moduleignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build/.moduleignore b/build/.moduleignore index 22c1b4fe48f..a2e1b715e1b 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -107,6 +107,14 @@ vscode-encrypt/binding.gyp vscode-encrypt/README.md !vscode-encrypt/build/Release/vscode-encrypt-native.node +vscode-policy-watcher/build/** +vscode-policy-watcher/.husky/** +vscode-policy-watcher/src/** +vscode-policy-watcher/binding.gyp +vscode-policy-watcher/README.md +vscode-policy-watcher/index.d.ts +!vscode-policy-watcher/build/Release/vscode-policy-watcher.node + vscode-windows-ca-certs/**/* !vscode-windows-ca-certs/package.json !vscode-windows-ca-certs/**/*.node From 56eca91b26ff4e25f548aae1969990cfa6716544 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 18 May 2022 14:13:58 +0200 Subject: [PATCH 29/34] add vscode-policy-watcher dependency, support registerPolicyDefinitions --- package.json | 2 +- src/vs/code/electron-main/main.ts | 4 +- src/vs/code/node/cliProcessMain.ts | 6 +- .../policy/node/nativePolicyService.ts | 68 ++++++++++++++++++ .../policy/node/windowsPolicyService.ts | 70 ------------------- yarn.lock | 8 +-- 6 files changed, 79 insertions(+), 79 deletions(-) create mode 100644 src/vs/platform/policy/node/nativePolicyService.ts delete mode 100644 src/vs/platform/policy/node/windowsPolicyService.ts diff --git a/package.json b/package.json index ed114a6ceac..412567abe45 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "tas-client-umd": "0.1.5", "v8-inspect-profiler": "^0.1.0", "vscode-oniguruma": "1.6.1", + "vscode-policy-watcher": "^1.1.0", "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", @@ -221,7 +222,6 @@ }, "optionalDependencies": { "@vscode/windows-registry": "1.0.6", - "vscode-policy-watcher": "^1.0.0", "windows-foreground-love": "0.4.0", "windows-mutex": "0.4.1", "windows-process-tree": "0.3.3" diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index d1563aebe63..cb41f40555e 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -63,7 +63,7 @@ import { StateMainService } from 'vs/platform/state/electron-main/stateMainServi import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; -import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; +import { NativePolicyService } from 'vs/platform/policy/node/nativePolicyService'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; /** @@ -170,7 +170,7 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Policy - const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) + const policyService = isWindows && productService.win32RegValueName ? new NativePolicyService(productService.win32RegValueName) : environmentMainService.policyFile ? new FilePolicyService(environmentMainService.policyFile, fileService, logService) : new NullPolicyService(); services.set(IPolicyService, policyService); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index f0151458057..bc630b7cae8 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -42,7 +42,7 @@ import { ConsoleLogger, getLogLevel, ILogger, ILogService, LogLevel, MultiplexLo import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; -import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; +import { NativePolicyService } from 'vs/platform/policy/node/nativePolicyService'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; @@ -133,7 +133,9 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // Policy - const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new NativePolicyService(productService.win32RegValueName) + : environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) + : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration diff --git a/src/vs/platform/policy/node/nativePolicyService.ts b/src/vs/platform/policy/node/nativePolicyService.ts new file mode 100644 index 00000000000..10cd2ee5446 --- /dev/null +++ b/src/vs/platform/policy/node/nativePolicyService.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { Iterable } from 'vs/base/common/iterator'; +import { Throttler } from 'vs/base/common/async'; +import type { Watcher } from 'vscode-policy-watcher'; + +export class NativePolicyService implements IPolicyService { + + readonly _serviceBrand: undefined; + + private policyDefinitions: IStringDictionary = {}; + private readonly policies = new Map(); + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + private throttler = new Throttler(); + private watcher: Watcher | undefined; + + constructor(private readonly productName: string) { } + + async registerPolicyDefinitions(policyDefinitions: IStringDictionary): Promise> { + const size = Object.keys(this.policyDefinitions).length; + this.policyDefinitions = { ...policyDefinitions, ...this.policyDefinitions }; + + if (size !== Object.keys(this.policyDefinitions).length) { + await this.throttler.queue(async () => { + this.watcher?.dispose(); + + const { createWatcher } = await import('vscode-policy-watcher'); + + await new Promise(c => { + this.watcher = createWatcher(this.productName, policyDefinitions, update => { + for (const key in update) { + const value = update[key] as any; + + if (value === undefined) { + this.policies.delete(key); + } else { + this.policies.set(key, value); + } + } + + this._onDidChange.fire(Object.keys(update)); + c(); + }); + }); + }); + } + + return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); + } + + getPolicyValue(name: PolicyName): PolicyValue | undefined { + return this.policies.get(name); + } + + dispose(): void { + this._onDidChange.dispose(); + this.watcher?.dispose(); + } +} diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts deleted file mode 100644 index 23439893dc8..00000000000 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; -import { createWatcher, Watcher } from 'vscode-policy-watcher'; -import { IStringDictionary } from 'vs/base/common/collections'; -import { Iterable } from 'vs/base/common/iterator'; - -export class WindowsPolicyService extends Disposable implements IPolicyService { - - readonly _serviceBrand: undefined; - - private readonly policies = new Map(); - private init: Promise | undefined; - - private readonly _onDidChange = new Emitter(); - readonly onDidChange = this._onDidChange.event; - - constructor(private readonly productName: string) { - super(); - } - - async registerPolicyDefinitions(policies: IStringDictionary): Promise> { - if (!this.init) { - this.init = new Promise(c => { - let first = true; - - const watcher = createWatcher(this.productName, policies, update => { - for (const key in update) { - const value = update[key] as any; - - if (value === undefined) { - this.policies.delete(key); - } else { - this.policies.set(key, value); - } - } - - if (first) { - first = false; - c(watcher); - } else { - this._onDidChange.fire(Object.keys(update)); - } - }); - - this._register(watcher); - }); - - await this.init; - } else { - const watcher = await this.init; - const promise = Event.toPromise(this.onDidChange); - watcher.addPolicies(policies); - await promise; - } - - // TODO@joao: heavy cleanup - - return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); - } - - getPolicyValue(name: PolicyName): PolicyValue | undefined { - return this.policies.get(name); - } -} diff --git a/yarn.lock b/yarn.lock index ac4091e65aa..36cf507aa65 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11796,10 +11796,10 @@ vscode-oniguruma@1.6.1: resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz#2bf4dfcfe3dd2e56eb549a3068c8ee39e6c30ce5" integrity sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ== -vscode-policy-watcher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vscode-policy-watcher/-/vscode-policy-watcher-1.0.0.tgz#08138a5ab15a1d6c021df51716a4daa97d74b471" - integrity sha512-j9nWBInu9wPW+3Uw/flYXP0kTewTqTfM6ADC24stqxURCAGOWxYz3ArBo21o+3zOrxpfCjc5mCbUNfLk+6IHuA== +vscode-policy-watcher@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vscode-policy-watcher/-/vscode-policy-watcher-1.1.0.tgz#2921353c5080b3452929f1e350b9fab9ff852cc9" + integrity sha512-yPvy3Or66H0l8/FyWbJeGxpWW3GDZf65EIT7fqp1Ethdz4ecEnHThuHti7SlfdRJTf5qnifrX7af02INiHqDMA== dependencies: bindings "^1.5.0" node-addon-api "*" From 089ef919ce457b047df2348360b0981b170ccb98 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 18 May 2022 14:34:19 +0200 Subject: [PATCH 30/34] update native policy service --- .../policy/node/nativePolicyService.ts | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/vs/platform/policy/node/nativePolicyService.ts b/src/vs/platform/policy/node/nativePolicyService.ts index 10cd2ee5446..9011bbf1534 100644 --- a/src/vs/platform/policy/node/nativePolicyService.ts +++ b/src/vs/platform/policy/node/nativePolicyService.ts @@ -8,35 +8,34 @@ import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/pl import { IStringDictionary } from 'vs/base/common/collections'; import { Iterable } from 'vs/base/common/iterator'; import { Throttler } from 'vs/base/common/async'; -import type { Watcher } from 'vscode-policy-watcher'; +import { createWatcher, Watcher } from 'vscode-policy-watcher'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -export class NativePolicyService implements IPolicyService { +export class NativePolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; private policyDefinitions: IStringDictionary = {}; private readonly policies = new Map(); - private readonly _onDidChange = new Emitter(); + private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange = this._onDidChange.event; private throttler = new Throttler(); - private watcher: Watcher | undefined; + private watcher = this._register(new MutableDisposable()); - constructor(private readonly productName: string) { } + constructor(private readonly productName: string) { + super(); + } async registerPolicyDefinitions(policyDefinitions: IStringDictionary): Promise> { const size = Object.keys(this.policyDefinitions).length; this.policyDefinitions = { ...policyDefinitions, ...this.policyDefinitions }; if (size !== Object.keys(this.policyDefinitions).length) { - await this.throttler.queue(async () => { - this.watcher?.dispose(); - - const { createWatcher } = await import('vscode-policy-watcher'); - - await new Promise(c => { - this.watcher = createWatcher(this.productName, policyDefinitions, update => { + await this.throttler.queue(() => new Promise((c, e) => { + try { + this.watcher.value = createWatcher(this.productName, policyDefinitions, update => { for (const key in update) { const value = update[key] as any; @@ -50,8 +49,10 @@ export class NativePolicyService implements IPolicyService { this._onDidChange.fire(Object.keys(update)); c(); }); - }); - }); + } catch (err) { + e(err); + } + })); } return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); @@ -60,9 +61,4 @@ export class NativePolicyService implements IPolicyService { getPolicyValue(name: PolicyName): PolicyValue | undefined { return this.policies.get(name); } - - dispose(): void { - this._onDidChange.dispose(); - this.watcher?.dispose(); - } } From 6ccc8509acb20e6c7b630acf86d9bb153564a9c3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 May 2022 11:28:57 +0200 Subject: [PATCH 31/34] reduce roundtrps - send policies data to workbench and shared process --- .../sharedProcess/sharedProcessMain.ts | 4 +- .../policy/common/filePolicyService.ts | 35 ++--------- src/vs/platform/policy/common/policy.ts | 41 +++++++++++- src/vs/platform/policy/common/policyIpc.ts | 35 +++++------ .../policy/node/nativePolicyService.ts | 62 +++++++------------ .../electron-main/sharedProcess.ts | 5 +- .../sharedProcess/node/sharedProcess.ts | 4 ++ src/vs/platform/window/common/window.ts | 3 + .../platform/windows/electron-main/window.ts | 3 + .../electron-main/windowsMainService.ts | 5 +- .../electron-sandbox/desktop.main.ts | 4 +- 11 files changed, 102 insertions(+), 99 deletions(-) diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 8a2cfbc36dc..243729c4e37 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -101,7 +101,7 @@ import { IV8InspectProfilingService } from 'vs/platform/profiling/common/profili import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; -import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; class SharedProcessMain extends Disposable { @@ -183,7 +183,7 @@ class SharedProcessMain extends Disposable { services.set(IMainProcessService, mainProcessService); // Policies - const policyService = new PolicyChannelClient(mainProcessService.getChannel('policy')); + const policyService = this.configuration.policiesData ? new PolicyChannelClient(this.configuration.policiesData, mainProcessService.getChannel('policy')) : new NullPolicyService(); services.set(IPolicyService, policyService); // Environment diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 4dadb9a1716..71d2ed5fd87 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -5,14 +5,13 @@ import { ThrottledDelayer } from 'vs/base/common/async'; import { IStringDictionary } from 'vs/base/common/collections'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; -import { Disposable } from 'vs/base/common/lifecycle'; import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { AbstractPolicyService, IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; function keysDiff(a: Map, b: Map): string[] { const result: string[] = []; @@ -26,15 +25,7 @@ function keysDiff(a: Map, b: Map): string[] { return result; } -export class FilePolicyService extends Disposable implements IPolicyService { - - readonly _serviceBrand: undefined; - - private readonly policyNames: Set = new Set(); - private policies = new Map(); - - private readonly _onDidChange = new Emitter(); - readonly onDidChange = this._onDidChange.event; +export class FilePolicyService extends AbstractPolicyService implements IPolicyService { private readonly throttledDelayer = this._register(new ThrottledDelayer(500)); @@ -50,18 +41,8 @@ export class FilePolicyService extends Disposable implements IPolicyService { this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.refresh()))); } - async registerPolicyDefinitions(policies: IStringDictionary): Promise> { - let hasNewPolicies = false; - for (const key of Object.keys(policies)) { - if (!this.policyNames.has(key)) { - hasNewPolicies = true; - this.policyNames.add(key); - } - } - if (hasNewPolicies) { - await this.refresh(); - } - return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); + protected async initializePolicies(policyDefinitions: IStringDictionary): Promise { + await this.refresh(); } private async read(): Promise> { @@ -76,7 +57,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { } for (const key of Object.keys(raw)) { - if (this.policyNames.has(key)) { + if (this.policyDefinitions[key]) { policies.set(key, raw[key]); } } @@ -98,8 +79,4 @@ export class FilePolicyService extends Disposable implements IPolicyService { this._onDidChange.fire(diff); } } - - getPolicyValue(name: PolicyName): PolicyValue | undefined { - return this.policies.get(name); - } } diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 21ef3302bb0..d02ddfb77ef 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IStringDictionary } from 'vs/base/common/collections'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Iterable } from 'vs/base/common/iterator'; +import { Disposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export type PolicyName = string; -export type PolicyValue = string | boolean; +export type PolicyValue = string | number; export type PolicyDefinition = { type: 'string' | 'number' }; export const IPolicyService = createDecorator('policy'); @@ -17,8 +19,40 @@ export interface IPolicyService { readonly _serviceBrand: undefined; readonly onDidChange: Event; - registerPolicyDefinitions(policies: IStringDictionary): Promise>; + registerPolicyDefinitions(policyDefinitions: IStringDictionary): Promise>; getPolicyValue(name: PolicyName): PolicyValue | undefined; + serialize(): IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }> | undefined; +} + +export abstract class AbstractPolicyService extends Disposable implements IPolicyService { + readonly _serviceBrand: undefined; + + protected policyDefinitions: IStringDictionary = {}; + protected policies = new Map(); + + protected readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + + async registerPolicyDefinitions(policyDefinitions: IStringDictionary): Promise> { + const size = Object.keys(this.policyDefinitions).length; + this.policyDefinitions = { ...policyDefinitions, ...this.policyDefinitions }; + + if (size !== Object.keys(this.policyDefinitions).length) { + await this.initializePolicies(policyDefinitions); + } + + return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); + } + + getPolicyValue(name: PolicyName): PolicyValue | undefined { + return this.policies.get(name); + } + + serialize(): IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }> { + return Iterable.reduce<[PolicyName, PolicyDefinition], IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>>(Object.entries(this.policyDefinitions), (r, [name, definition]) => ({ ...r, [name]: { definition, value: this.policies.get(name)! } }), {}); + } + + protected abstract initializePolicies(policyDefinitions: IStringDictionary): Promise; } export class NullPolicyService implements IPolicyService { @@ -26,4 +60,5 @@ export class NullPolicyService implements IPolicyService { readonly onDidChange = Event.None; async registerPolicyDefinitions() { return {}; } getPolicyValue() { return undefined; } + serialize() { return undefined; } } diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts index 177b1d51ae6..cdca636e999 100644 --- a/src/vs/platform/policy/common/policyIpc.ts +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { IStringDictionary } from 'vs/base/common/collections'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { AbstractPolicyService, IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; export class PolicyChannel implements IServerChannel { @@ -29,7 +29,7 @@ export class PolicyChannel implements IServerChannel { call(_: unknown, command: string, arg?: any): Promise { switch (command) { - case 'initialize': return this.service.registerPolicyDefinitions(arg as IStringDictionary); + case 'registerPolicyDefinitions': return this.service.registerPolicyDefinitions(arg as IStringDictionary); } throw new Error(`Call not found: ${command}`); @@ -40,16 +40,17 @@ export class PolicyChannel implements IServerChannel { } } -export class PolicyChannelClient implements IPolicyService { +export class PolicyChannelClient extends AbstractPolicyService implements IPolicyService { - declare readonly _serviceBrand: undefined; - - private policies = new Map(); - - private readonly _onDidChange = new Emitter(); - readonly onDidChange: Event = this._onDidChange.event; - - constructor(private readonly channel: IChannel) { + constructor(policiesData: IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>, private readonly channel: IChannel) { + super(); + for (const name in policiesData) { + const { definition, value } = policiesData[name]; + this.policyDefinitions[name] = definition; + if (value !== undefined) { + this.policies.set(name, value); + } + } this.channel.listen('onDidChange')(policies => { for (const name in policies) { const value = policies[name as keyof typeof policies]; @@ -65,17 +66,11 @@ export class PolicyChannelClient implements IPolicyService { }); } - async registerPolicyDefinitions(policies: IStringDictionary): Promise> { - const result = await this.channel.call<{ [name: PolicyName]: PolicyValue }>('initialize', policies); - + protected async initializePolicies(policyDefinitions: IStringDictionary): Promise { + const result = await this.channel.call<{ [name: PolicyName]: PolicyValue }>('registerPolicyDefinitions', policyDefinitions); for (const name in result) { this.policies.set(name, result[name]); } - - return result; } - getPolicyValue(name: PolicyName): PolicyValue | undefined { - return this.policies.get(name); - } } diff --git a/src/vs/platform/policy/node/nativePolicyService.ts b/src/vs/platform/policy/node/nativePolicyService.ts index 9011bbf1534..4d0e46d9d06 100644 --- a/src/vs/platform/policy/node/nativePolicyService.ts +++ b/src/vs/platform/policy/node/nativePolicyService.ts @@ -3,23 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; -import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { AbstractPolicyService, IPolicyService, PolicyDefinition } from 'vs/platform/policy/common/policy'; import { IStringDictionary } from 'vs/base/common/collections'; -import { Iterable } from 'vs/base/common/iterator'; import { Throttler } from 'vs/base/common/async'; import { createWatcher, Watcher } from 'vscode-policy-watcher'; -import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { MutableDisposable } from 'vs/base/common/lifecycle'; -export class NativePolicyService extends Disposable implements IPolicyService { - - readonly _serviceBrand: undefined; - - private policyDefinitions: IStringDictionary = {}; - private readonly policies = new Map(); - - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; +export class NativePolicyService extends AbstractPolicyService implements IPolicyService { private throttler = new Throttler(); private watcher = this._register(new MutableDisposable()); @@ -28,37 +18,27 @@ export class NativePolicyService extends Disposable implements IPolicyService { super(); } - async registerPolicyDefinitions(policyDefinitions: IStringDictionary): Promise> { - const size = Object.keys(this.policyDefinitions).length; - this.policyDefinitions = { ...policyDefinitions, ...this.policyDefinitions }; + protected async initializePolicies(policyDefinitions: IStringDictionary): Promise { + await this.throttler.queue(() => new Promise((c, e) => { + try { + this.watcher.value = createWatcher(this.productName, policyDefinitions, update => { + for (const key in update) { + const value = update[key] as any; - if (size !== Object.keys(this.policyDefinitions).length) { - await this.throttler.queue(() => new Promise((c, e) => { - try { - this.watcher.value = createWatcher(this.productName, policyDefinitions, update => { - for (const key in update) { - const value = update[key] as any; - - if (value === undefined) { - this.policies.delete(key); - } else { - this.policies.set(key, value); - } + if (value === undefined) { + this.policies.delete(key); + } else { + this.policies.set(key, value); } + } - this._onDidChange.fire(Object.keys(update)); - c(); - }); - } catch (err) { - e(err); - } - })); - } - - return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); + this._onDidChange.fire(Object.keys(update)); + c(); + }); + } catch (err) { + e(err); + } + })); } - getPolicyValue(name: PolicyName): PolicyValue | undefined { - return this.policies.get(name); - } } diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index f7046a4d756..0ccd16e61e5 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -22,6 +22,7 @@ import { ISharedProcessWorkerConfiguration } from 'vs/platform/sharedProcess/com import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { WindowError } from 'vs/platform/window/electron-main/window'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; export class SharedProcess extends Disposable implements ISharedProcess { @@ -39,6 +40,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, + @IPolicyService private readonly policyService: IPolicyService, @IThemeMainService private readonly themeMainService: IThemeMainService, @IProtocolMainService private readonly protocolMainService: IProtocolMainService ) { @@ -242,7 +244,8 @@ export class SharedProcess extends Disposable implements ISharedProcess { userEnv: this.userEnv, args: this.environmentMainService.args, logLevel: this.logService.getLevel(), - product + product, + policiesData: this.policyService.serialize() }); // Load with config diff --git a/src/vs/platform/sharedProcess/node/sharedProcess.ts b/src/vs/platform/sharedProcess/node/sharedProcess.ts index 70c028b7a11..5c0265c6c4f 100644 --- a/src/vs/platform/sharedProcess/node/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/node/sharedProcess.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IStringDictionary } from 'vs/base/common/collections'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { LogLevel } from 'vs/platform/log/common/log'; +import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; export interface ISharedProcess { @@ -24,4 +26,6 @@ export interface ISharedProcessConfiguration extends ISandboxConfiguration { readonly logLevel: LogLevel; readonly backupWorkspacesPath: string; + + readonly policiesData?: IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>; } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 2c9591a2bec..afac7f93ce7 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IStringDictionary } from 'vs/base/common/collections'; import { PerformanceMark } from 'vs/base/common/performance'; import { isLinux, isMacintosh, isNative, isWeb } from 'vs/base/common/platform'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -12,6 +13,7 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { FileType } from 'vs/platform/files/common/files'; import { LogLevel } from 'vs/platform/log/common/log'; +import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; @@ -289,6 +291,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native filesToWait?: IPathsToWaitFor; os: IOSConfiguration; + policiesData?: IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>; } /** diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index dd3fb88731f..3b8ba525827 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -39,6 +39,7 @@ import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, isSingleFolderW import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { IWindowState, ICodeWindow, ILoadEvent, WindowMode, WindowError, LoadReason, defaultWindowState } from 'vs/platform/window/electron-main/window'; import { Color } from 'vs/base/common/color'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; export interface IWindowCreationOptions { state: IWindowState; @@ -149,6 +150,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { config: IWindowCreationOptions, @ILogService private readonly logService: ILogService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, + @IPolicyService private readonly policyService: IPolicyService, @IFileService private readonly fileService: IFileService, @IGlobalStorageMainService private readonly globalStorageMainService: IGlobalStorageMainService, @IStorageMainService private readonly storageMainService: IStorageMainService, @@ -870,6 +872,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } configuration.isInitialStartup = false; // since this is a reload + configuration.policiesData = this.policyService.serialize(); // set policies data again // Load config this.load(configuration, { isReload: true, disableExtensions: cli?.['disable-extensions'] }); diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index ec6d1848956..81ecd35e990 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -51,6 +51,7 @@ import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electro import { ICodeWindow, UnloadReason } from 'vs/platform/window/electron-main/window'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; //#region Helper Interfaces @@ -192,6 +193,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly initialUserEnv: IProcessEnvironment, @ILogService private readonly logService: ILogService, @IStateMainService private readonly stateMainService: IStateMainService, + @IPolicyService private readonly policyService: IPolicyService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, @@ -1320,7 +1322,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic autoDetectHighContrast: windowConfig?.autoDetectHighContrast ?? true, autoDetectColorScheme: windowConfig?.autoDetectColorScheme ?? false, accessibilitySupport: app.accessibilitySupportEnabled, - colorScheme: this.themeMainService.getColorScheme() + colorScheme: this.themeMainService.getColorScheme(), + policiesData: this.policyService.serialize(), }; let window: ICodeWindow | undefined; diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 42ada97cea0..88955896dce 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -51,7 +51,7 @@ import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/electron-sandbox/diskFileSystemProvider'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; -import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; export class DesktopMain extends Disposable { @@ -158,7 +158,7 @@ export class DesktopMain extends Disposable { serviceCollection.set(IMainProcessService, mainProcessService); // Policies - const policyService = new PolicyChannelClient(mainProcessService.getChannel('policy')); + const policyService = this.configuration.policiesData ? new PolicyChannelClient(this.configuration.policiesData, mainProcessService.getChannel('policy')) : new NullPolicyService(); serviceCollection.set(IPolicyService, policyService); // Product From 7ce1b0f88502c32a6024b56494832f6bf255c1fb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 May 2022 11:43:33 +0200 Subject: [PATCH 32/34] perf improvements --- .../configuration/common/configurationService.ts | 8 ++++---- .../configuration/common/configurations.ts | 16 ++++++++++++++-- .../browser/configurationService.ts | 8 ++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index a1bd9906ee0..99da4050566 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -10,10 +10,10 @@ import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationChangeEvent, ConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; -import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; +import { DefaultConfiguration, IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { @@ -21,7 +21,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe private configuration: Configuration; private readonly defaultConfiguration: DefaultConfiguration; - private readonly policyConfiguration: PolicyConfiguration; + private readonly policyConfiguration: IPolicyConfiguration; private readonly userConfiguration: UserSettings; private readonly reloadConfigurationScheduler: RunOnceScheduler; @@ -36,7 +36,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe ) { super(); this.defaultConfiguration = this._register(new DefaultConfiguration()); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); + this.policyConfiguration = policyService instanceof NullPolicyService ? new NullPolicyConfiguration() : this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel()); diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 6099234ef8e..81133870a7a 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -5,7 +5,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { IStringDictionary } from 'vs/base/common/collections'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; import { isEmptyObject } from 'vs/base/common/types'; @@ -78,7 +78,19 @@ export class DefaultConfigurationModel extends ConfigurationModel { } } -export class PolicyConfiguration extends Disposable { +export interface IPolicyConfiguration { + readonly onDidChangeConfiguration: Event; + readonly configurationModel: ConfigurationModel; + initialize(): Promise; +} + +export class NullPolicyConfiguration implements IPolicyConfiguration { + readonly onDidChangeConfiguration = Event.None; + readonly configurationModel = new ConfigurationModel(); + async initialize() { return this.configurationModel; } +} + +export class PolicyConfiguration extends Disposable implements IPolicyConfiguration { private readonly _onDidChangeConfiguration = this._register(new Emitter()); readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 26f97a95502..d32d7e978bb 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -13,7 +13,7 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder, IWorkspaceFoldersWillChangeEvent, IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier, IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; +import { IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -40,7 +40,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { isUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; -import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; class Workspace extends BaseWorkspace { initialized: boolean = false; @@ -57,7 +57,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private _configuration: Configuration; private initialized: boolean = false; private readonly defaultConfiguration: DefaultConfiguration; - private readonly policyConfiguration: PolicyConfiguration; + private readonly policyConfiguration: IPolicyConfiguration; private localUserConfiguration: UserConfiguration; private remoteUserConfiguration: RemoteUserConfiguration | null = null; private workspaceConfiguration: WorkspaceConfiguration; @@ -114,7 +114,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.initRemoteUserConfigurationBarrier = new Barrier(); this.completeWorkspaceBarrier = new Barrier(); this.defaultConfiguration = this._register(new DefaultConfiguration(configurationCache, environmentService)); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); + this.policyConfiguration = policyService instanceof NullPolicyService ? new NullPolicyConfiguration() : this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.configurationCache = configurationCache; this.fileService = fileService; this.uriIdentityService = uriIdentityService; From 04e34f25549ca272802d8532a5616b48ef262b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 19 May 2022 13:56:04 +0200 Subject: [PATCH 33/34] dispose native policy service on shutdown --- src/vs/code/electron-main/main.ts | 10 ++++++---- src/vs/code/node/cliProcessMain.ts | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index cb41f40555e..cfa944e76c8 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -65,6 +65,7 @@ import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron- import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { NativePolicyService } from 'vs/platform/policy/node/nativePolicyService'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; +import { DisposableStore } from 'vs/base/common/lifecycle'; /** * The main VS Code entry point. @@ -142,6 +143,8 @@ class CodeMain { private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, IPolicyService, ConfigurationService, StateMainService, BufferLogService, IProductService] { const services = new ServiceCollection(); + const disposables = new DisposableStore(); + process.once('exit', () => disposables.dispose()); // Product const productService = { _serviceBrand: undefined, ...product }; @@ -156,8 +159,7 @@ class CodeMain { // we are the only instance running, otherwise we'll have concurrent // log file access on Windows (https://github.com/microsoft/vscode/issues/41218) const bufferLogService = new BufferLogService(); - const logService = new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentMainService)), bufferLogService]); - process.once('exit', () => logService.dispose()); + const logService = disposables.add(new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentMainService)), bufferLogService])); services.set(ILogService, logService); // Files @@ -170,8 +172,8 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Policy - const policyService = isWindows && productService.win32RegValueName ? new NativePolicyService(productService.win32RegValueName) - : environmentMainService.policyFile ? new FilePolicyService(environmentMainService.policyFile, fileService, logService) + const policyService = isWindows && productService.win32RegValueName ? disposables.add(new NativePolicyService(productService.win32RegValueName)) + : environmentMainService.policyFile ? disposables.add(new FilePolicyService(environmentMainService.policyFile, fileService, logService)) : new NullPolicyService(); services.set(IPolicyService, policyService); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index bc630b7cae8..6c197b17661 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -133,8 +133,8 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // Policy - const policyService = isWindows && productService.win32RegValueName ? new NativePolicyService(productService.win32RegValueName) - : environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) + const policyService = isWindows && productService.win32RegValueName ? this._register(new NativePolicyService(productService.win32RegValueName)) + : environmentService.policyFile ? this._register(new FilePolicyService(environmentService.policyFile, fileService, logService)) : new NullPolicyService(); services.set(IPolicyService, policyService); From c3bddb2a4379ba203907f23c911504b639aeda17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 23 May 2022 16:01:40 +0200 Subject: [PATCH 34/34] adopt policy from configuration service --- src/vs/code/electron-main/app.ts | 2 +- .../electron-main/abstractUpdateService.ts | 6 ++-- .../electron-main/updateService.darwin.ts | 4 +-- .../electron-main/updateService.win32.ts | 28 ------------------- 4 files changed, 6 insertions(+), 34 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index a162ae7db70..2d7e71bef34 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -1148,7 +1148,7 @@ export class CodeApplication extends Disposable { // Initialize update service const updateService = accessor.get(IUpdateService); if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) { - await updateService.initialize(); + updateService.initialize(); } // Start to fetch shell environment (if needed) after window has opened diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 86737bd1657..42b9e8cee7a 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -57,7 +57,7 @@ export abstract class AbstractUpdateService implements IUpdateService { * optimization, to avoid using extra CPU cycles before first window open. * https://github.com/microsoft/vscode/issues/89784 */ - async initialize(): Promise { + initialize(): void { if (!this.environmentMainService.isBuilt) { return; // updates are never enabled when running out of sources } @@ -72,7 +72,7 @@ export abstract class AbstractUpdateService implements IUpdateService { return; } - const updateMode = await this.getUpdateMode(); + const updateMode = this.getUpdateMode(); const quality = this.getProductQuality(updateMode); if (!quality) { @@ -104,7 +104,7 @@ export abstract class AbstractUpdateService implements IUpdateService { } } - protected async getUpdateMode(): Promise<'none' | 'manual' | 'start' | 'default'> { + protected getUpdateMode(): 'none' | 'manual' | 'start' | 'default' { return getMigratedSettingValue<'none' | 'manual' | 'start' | 'default'>(this.configurationService, 'update.mode', 'update.channel'); } diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 688ea356ef1..0c008ea8504 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -38,8 +38,8 @@ export class DarwinUpdateService extends AbstractUpdateService { super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } - override async initialize(): Promise { - await super.initialize(); + override initialize(): void { + super.initialize(); this.onRawError(this.onError, this, this.disposables); this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables); this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables); diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index e3661ce4bbc..fa2490cdfca 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -47,14 +47,6 @@ function getUpdateType(): UpdateType { return _updateType; } -function validateUpdateModeValue(value: string | undefined): 'none' | 'manual' | 'start' | 'default' | undefined { - if (value === 'none' || value === 'manual' || value === 'start' || value === 'default') { - return value; - } else { - return undefined; - } -} - export class Win32UpdateService extends AbstractUpdateService { private availableUpdate: IAvailableUpdate | undefined; @@ -79,26 +71,6 @@ export class Win32UpdateService extends AbstractUpdateService { super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } - protected override async getUpdateMode(): Promise<'none' | 'manual' | 'start' | 'default'> { - if (this.productService.win32RegValueName) { - const policyKey = `Software\\Policies\\Microsoft\\${this.productService.win32RegValueName}`; - const [hklm, hkcu] = await Promise.all([ - this.nativeHostMainService.windowsGetStringRegKey(undefined, 'HKEY_LOCAL_MACHINE', policyKey, 'UpdateMode').then(validateUpdateModeValue), - this.nativeHostMainService.windowsGetStringRegKey(undefined, 'HKEY_CURRENT_USER', policyKey, 'UpdateMode').then(validateUpdateModeValue) - ]); - - if (hklm) { - this.logService.info(`update#getUpdateMode: 'UpdateMode' policy defined in 'HKLM\\${policyKey}':`, hklm); - return hklm; - } else if (hkcu) { - this.logService.info(`update#getUpdateMode: 'UpdateMode' policy defined in 'HKCU\\${policyKey}':`, hkcu); - return hkcu; - } - } - - return await super.getUpdateMode(); - } - protected buildUpdateFeedUrl(quality: string): string | undefined { let platform = 'win32';