diff --git a/.eslintrc.json b/.eslintrc.json index 19ca109b..9bf7de2d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -80,6 +80,7 @@ "src/common/config/buildConfig.js", "src/common/config/pastDefaultPreferences.js", "src/common/config/upgradePreferences.js", + "src/common/config/RegistryConfig.js", "src/common/osVersion.js", "src/common/config/defaultPreferences.js", "src/common/JsonFileManager.js", diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx index 27742fa8..90d4bde2 100644 --- a/src/browser/components/SettingsPage.jsx +++ b/src/browser/components/SettingsPage.jsx @@ -19,7 +19,7 @@ import AutoSaveIndicator from './AutoSaveIndicator.jsx'; const CONFIG_TYPE_SERVERS = 'servers'; const CONFIG_TYPE_APP_OPTIONS = 'appOptions'; -const config = new Config(remote.app.getPath('userData') + '/config.json'); +const config = new Config(remote.app.getPath('userData') + '/config.json', remote.getCurrentWindow().registryConfigData); function backToIndex(index) { const target = typeof index === 'undefined' ? 0 : index; @@ -340,7 +340,7 @@ export default class SettingsPage extends React.Component { addServer={this.addServer} allowTeamEdit={this.state.enableTeamModification} onTeamClick={(index) => { - backToIndex(index + this.state.buildTeams.length + this.state.GPOTeams.length); + backToIndex(index + this.state.buildTeams.length + this.state.registryTeams.length); }} /> @@ -618,7 +618,7 @@ export default class SettingsPage extends React.Component { bsStyle='link' style={settingsPage.close} onClick={this.handleCancel} - disabled={this.state.localTeams.length === 0} + disabled={this.state.teams.length === 0} > {'×'} diff --git a/src/browser/index.jsx b/src/browser/index.jsx index db9241a0..f69898b0 100644 --- a/src/browser/index.jsx +++ b/src/browser/index.jsx @@ -21,7 +21,7 @@ import Config from '../common/config'; import MainPage from './components/MainPage.jsx'; import {createDataURL as createBadgeDataURL} from './js/badge'; -const config = new Config(remote.app.getPath('userData') + '/config.json'); +const config = new Config(remote.app.getPath('userData') + '/config.json', remote.getCurrentWindow().registryConfigData); const teams = config.teams; diff --git a/src/common/config/RegistryConfig.js b/src/common/config/RegistryConfig.js new file mode 100644 index 00000000..cad10415 --- /dev/null +++ b/src/common/config/RegistryConfig.js @@ -0,0 +1,106 @@ +// Copyright (c) 2015-2016 Yuya Ochiai +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {EventEmitter} from 'events'; + +import WindowsRegistry from 'winreg'; + +const REGISTRY_HIVE_LIST = [WindowsRegistry.HKLM, WindowsRegistry.HKCU]; +const BASE_REGISTRY_KEY_PATH = '\\Software\\Policies\\Mattermost'; + +export default class RegistryConfig extends EventEmitter { + constructor() { + super(); + this.initialized = false; + this.data = { + teams: [], + }; + } + async init() { + if (process.platform === 'win32') { + // extract DefaultServerList from the registry + try { + const servers = await this.getServersListFromRegistry(); + if (servers.length) { + this.data.teams.push(...servers); + } + } catch (error) { + console.log('[RegistryConfig] Nothing retrieved for \'DefaultServerList\'', error); + } + + // extract EnableServerManagement from the registry + try { + const enableServerManagement = await this.getEnableServerManagementFromRegistry(); + if (enableServerManagement !== null) { + this.data.enableServerManagement = enableServerManagement; + } + } catch (error) { + console.log('[RegistryConfig] Nothing retrieved for \'EnableServerManagement\'', error); + } + + // extract EnableAutoUpdater from the registry + try { + const enableAutoUpdater = await this.getEnableAutoUpdatorFromRegistry(); + if (enableAutoUpdater !== null) { + this.data.enableAutoUpdater = enableAutoUpdater; + } + } catch (error) { + console.log('[RegistryConfig] Nothing retrieved for \'EnableAutoUpdater\'', error); + } + + this.initialized = true; + this.emit('update', this.data); + } + } + async getServersListFromRegistry() { + const defaultTeams = await this.getRegistryEntry(`${BASE_REGISTRY_KEY_PATH}\\DefaultServerList`); + return defaultTeams.flat(2).reduce((teams, team) => { + if (team) { + teams.push({ + name: team.name, + url: team.value, + }); + } + return teams; + }, []); + } + + async getEnableServerManagementFromRegistry() { + const entries = (await this.getRegistryEntry(BASE_REGISTRY_KEY_PATH, 'EnableServerManagement')); + const entry = entries.pop(); + return entry ? entry === '0x1' : null; + } + + async getEnableAutoUpdatorFromRegistry() { + const entries = (await this.getRegistryEntry(BASE_REGISTRY_KEY_PATH, 'EnableAutoUpdater')); + const entry = entries.pop(); + return entry ? entry === '0x1' : null; + } + + async getRegistryEntry(key, name) { + const results = []; + for (const hive of REGISTRY_HIVE_LIST) { + results.push(this.getRegistryEntryValues(new WindowsRegistry({hive, key}), name)); + } + const entryValues = await Promise.all(results); + return entryValues.filter((value) => value); + } + + getRegistryEntryValues(regKey, name) { + return new Promise((resolve) => { + regKey.values((error, items) => { + if (error || !items || !items.length) { + resolve(); + return; + } + if (name) { // looking for a single entry value + const registryItem = items.find((item) => item.name === name); + resolve(registryItem && registryItem.value ? registryItem.value : null); + } else { // looking for an entry list + resolve(items); + } + }); + }); + } +} diff --git a/src/common/config/index.js b/src/common/config/index.js index 457e1e63..f1757019 100644 --- a/src/common/config/index.js +++ b/src/common/config/index.js @@ -15,9 +15,10 @@ import buildConfig from './buildConfig'; * Handles loading and merging all sources of configuration as well as saving user provided config */ export default class Config extends EventEmitter { - constructor(configFilePath) { + constructor(configFilePath, registryConfigData = {teams: []}) { super(); this.configFilePath = configFilePath; + this.registryConfigData = registryConfigData; this.reload(); } @@ -35,8 +36,6 @@ export default class Config extends EventEmitter { this.localConfigData = this.loadLocalConfigFile(); this.localConfigData = this.checkForConfigUpdates(this.localConfigData); - this.GPOConfigData = this.loadGPOConfigData(); - this.regenerateCombinedConfigData(); this.emit('update', this.combinedData); @@ -77,6 +76,11 @@ export default class Config extends EventEmitter { } } + setRegistryConfigData(registryConfigData = {teams: []}) { + this.registryConfigData = Object.assign({}, registryConfigData); + this.reload(); + } + /** * Used to replace the existing config data with new config data * @@ -126,8 +130,8 @@ export default class Config extends EventEmitter { get buildData() { return this.buildConfigData; } - get GPOData() { - return this.GPOConfigData; + get registryData() { + return this.registryConfigData; } // convenience getters @@ -142,7 +146,7 @@ export default class Config extends EventEmitter { return this.localConfigData.teams; } get predefinedTeams() { - return [...this.buildConfigData.defaultTeams, ...this.GPOConfigData.teams]; + return [...this.buildConfigData.defaultTeams, ...this.registryConfigData.teams]; } get enableHardwareAcceleration() { return this.combinedData.enableHardwareAcceleration; @@ -216,23 +220,6 @@ export default class Config extends EventEmitter { return configData; } - /** - * Loads and returns config data defined in GPO for Windows - */ - loadGPOConfigData() { - const configData = { - teams: [], - enableServerManagement: true, - enableAutoUpdater: true, - }; - if (process.platform === 'win32') { - // - // TODO: GPO data needs to be retrieved here and merged into the local `configData` variable for return - // - } - return configData; - } - /** * Determines if locally stored data needs to be updated and upgrades as needed * @@ -257,7 +244,7 @@ export default class Config extends EventEmitter { */ regenerateCombinedConfigData() { // combine all config data in the correct order - this.combinedData = Object.assign({}, this.defaultConfigData, this.localConfigData, this.buildConfigData, this.GPOConfigData); + this.combinedData = Object.assign({}, this.defaultConfigData, this.localConfigData, this.buildConfigData, this.registryConfigData); // remove unecessary data pulled from default and build config delete this.combinedData.defaultTeam; @@ -271,9 +258,9 @@ export default class Config extends EventEmitter { combinedTeams.push(...this.buildConfigData.defaultTeams); } - // - add GPO defined teams, if any - if (this.GPOConfigData.teams && this.GPOConfigData.teams.length) { - combinedTeams.push(...this.GPOConfigData.teams); + // - add registry defined teams, if any + if (this.registryConfigData.teams && this.registryConfigData.teams.length) { + combinedTeams.push(...this.registryConfigData.teams); } // - add locally defined teams only if server management is enabled @@ -284,7 +271,7 @@ export default class Config extends EventEmitter { this.combinedData.teams = combinedTeams; this.combinedData.localTeams = this.localConfigData.teams; this.combinedData.buildTeams = this.buildConfigData.defaultTeams; - this.combinedData.GPOTeams = this.GPOConfigData.teams; + this.combinedData.registryTeams = this.registryConfigData.teams; } /** diff --git a/src/main.js b/src/main.js index 9ba1cb44..ea26ae1c 100644 --- a/src/main.js +++ b/src/main.js @@ -37,6 +37,7 @@ global.willAppQuit = false; app.setAppUserModelId('com.squirrel.mattermost.Mattermost'); // Use explicit AppUserModelID +import RegistryConfig from './common/config/RegistryConfig'; import Config from './common/config'; import CertificateStore from './main/certificateStore'; const certificateStore = CertificateStore.load(path.resolve(app.getPath('userData'), 'certificate.json')); @@ -63,7 +64,9 @@ let deeplinkingUrl = null; let scheme = null; let appState = null; let permissionManager = null; -let config = null; + +const registryConfig = new RegistryConfig(); +const config = new Config(app.getPath('userData') + '/config.json'); const argv = parseArgv(process.argv.slice(1)); const hideOnStartup = shouldBeHiddenOnStartup(argv); @@ -74,13 +77,20 @@ if (argv['data-dir']) { global.isDev = isDev && !argv.disableDevMode; -config = new Config(app.getPath('userData') + '/config.json'); - // can only call this before the app is ready if (config.enableHardwareAcceleration === false) { app.disableHardwareAcceleration(); } +registryConfig.on('update', (registryConfigData) => { + config.setRegistryConfigData(registryConfigData); + if (app.isReady() && mainWindow) { + mainWindow.registryConfigData = registryConfigData; + } +}); + +registryConfig.init(); + config.on('update', (configData) => { if (process.platform === 'win32' || process.platform === 'linux') { const appLauncher = new AutoLauncher(); diff --git a/src/package.json b/src/package.json index 6f4cc9f9..453725dd 100644 --- a/src/package.json +++ b/src/package.json @@ -24,6 +24,7 @@ "semver": "^5.5.0", "simple-spellchecker": "^0.9.8", "underscore": "^1.9.1", + "winreg": "^1.2.4", "yargs": "^3.32.0" } }