[MM-56389] Switch to registry-js native module for Windows registry reading (#3460)

This commit is contained in:
Devin Binnie
2025-07-18 10:42:44 -04:00
committed by GitHub
parent 7f91463c58
commit af7009535d
9 changed files with 520 additions and 214 deletions

View File

@@ -3,54 +3,55 @@
import RegistryConfig from 'common/config/RegistryConfig';
jest.mock('winreg', () => {
return jest.fn().mockImplementation(({hive, key}) => {
return {
values: (fn) => {
if (hive === 'correct-hive') {
fn(null, [
{
name: `${key}-name-1`,
value: `${key}-value-1`,
jest.mock('registry-js', () => {
return {
HKEY: {
HKEY_LOCAL_MACHINE: 'HKEY_LOCAL_MACHINE',
HKEY_CURRENT_USER: 'HKEY_CURRENT_USER',
},
enumerateValues: jest.fn().mockImplementation((hive, key) => {
if (hive === 'correct-hive') {
return [
{
name: `${key}-name-1`,
data: `${key}-value-1`,
},
},
{
name: `${key}-name-2`,
data: `${key}-value-2`,
},
];
} else if (hive === 'mattermost-hive') {
if (key.endsWith('DefaultServerList')) {
return [
{
name: `${key}-name-2`,
value: `${key}-value-2`,
name: 'server-1',
data: 'http://server-1.com',
},
]);
} else if (hive === 'mattermost-hive') {
if (key.endsWith('DefaultServerList')) {
fn(null, [
{
name: 'server-1',
value: 'http://server-1.com',
},
]);
} else {
fn(null, [
{
name: 'EnableServerManagement',
value: '0x1',
},
{
name: 'EnableAutoUpdater',
value: '0x1',
},
]);
}
} else if (hive === 'really-bad-hive') {
throw new Error('This is an error');
} else {
fn('Error', []);
];
}
},
};
});
return [
{
name: 'EnableServerManagement',
data: '0x1',
},
{
name: 'EnableAutoUpdater',
data: '0x1',
},
];
} else if (hive === 'really-bad-hive') {
throw new Error('This is an error');
}
return [];
}),
};
});
describe('common/config/RegistryConfig', () => {
it('should initialize correctly', async () => {
it('should initialize correctly', () => {
const originalPlatform = process.platform;
Object.defineProperty(process, 'platform', {
value: 'win32',
@@ -59,7 +60,7 @@ describe('common/config/RegistryConfig', () => {
const registryConfig = new RegistryConfig();
const originalFn = registryConfig.getRegistryEntryValues;
registryConfig.getRegistryEntryValues = (hive, key, name) => originalFn.apply(registryConfig, ['mattermost-hive', key, name, false]);
await registryConfig.init();
registryConfig.init();
Object.defineProperty(process, 'platform', {
value: originalPlatform,
@@ -76,32 +77,32 @@ describe('common/config/RegistryConfig', () => {
const registryConfig = new RegistryConfig();
it('should return correct values', () => {
expect(registryConfig.getRegistryEntryValues('correct-hive', 'correct-key', null, false)).resolves.toStrictEqual([
expect(registryConfig.getRegistryEntryValues('correct-hive', 'correct-key', null, false)).toStrictEqual([
{
name: 'correct-key-name-1',
value: 'correct-key-value-1',
data: 'correct-key-value-1',
},
{
name: 'correct-key-name-2',
value: 'correct-key-value-2',
data: 'correct-key-value-2',
},
]);
});
it('should return correct value by name', () => {
expect(registryConfig.getRegistryEntryValues('correct-hive', 'correct-key', 'correct-key-name-1', false)).resolves.toBe('correct-key-value-1');
expect(registryConfig.getRegistryEntryValues('correct-hive', 'correct-key', 'correct-key-name-1', false)).toBe('correct-key-value-1');
});
it('should return undefined with wrong name', () => {
expect(registryConfig.getRegistryEntryValues('correct-hive', 'correct-key', 'wrong-key-name-1', false)).resolves.toBe(undefined);
expect(registryConfig.getRegistryEntryValues('correct-hive', 'correct-key', 'wrong-key-name-1', false)).toBe(undefined);
});
it('should return undefined with bad hive', () => {
expect(registryConfig.getRegistryEntryValues('bad-hive', 'correct-key', null, false)).resolves.toBe(undefined);
expect(registryConfig.getRegistryEntryValues('bad-hive', 'correct-key', null, false)).toBe(undefined);
});
it('should call reject when an error occurs', () => {
expect(registryConfig.getRegistryEntryValues('really-bad-hive', 'correct-key', null, false)).rejects.toThrow(new Error('This is an error'));
it('should return undefined when an error occurs', () => {
expect(registryConfig.getRegistryEntryValues('really-bad-hive', 'correct-key', null, false)).toBe(undefined);
});
});
});

View File

@@ -3,16 +3,16 @@
// See LICENSE.txt for license information.
import {EventEmitter} from 'events';
import WindowsRegistry from 'winreg';
import WindowsRegistryUTF8 from 'winreg-utf8';
import type {RegistryValue} from 'registry-js';
import {HKEY, enumerateValues} from 'registry-js';
import {Logger} from 'common/log';
import type {RegistryConfig as RegistryConfigType, Server} from 'types/config';
const log = new Logger('RegistryConfig');
const REGISTRY_HIVE_LIST = [WindowsRegistry.HKLM, WindowsRegistry.HKCU];
const BASE_REGISTRY_KEY_PATH = '\\Software\\Policies\\Mattermost';
const REGISTRY_HIVE_LIST = [HKEY.HKEY_LOCAL_MACHINE, HKEY.HKEY_CURRENT_USER];
const BASE_REGISTRY_KEY_PATH = 'SOFTWARE\\Policies\\Mattermost';
export const REGISTRY_READ_EVENT = 'registry-read';
/**
@@ -31,15 +31,15 @@ export default class RegistryConfig extends EventEmitter {
}
/**
* Triggers loading data from Windows registry, supports async/await
* Triggers loading data from Windows registry
*
* @emits {update} emitted once all data has been loaded from the registry
*/
async init() {
init() {
if (process.platform === 'win32') {
// extract DefaultServerList from the registry
try {
const servers = await this.getServersListFromRegistry();
const servers = this.getServersListFromRegistry();
if (servers.length) {
this.data.servers!.push(...servers);
}
@@ -49,7 +49,7 @@ export default class RegistryConfig extends EventEmitter {
// extract EnableServerManagement from the registry
try {
const enableServerManagement = await this.getEnableServerManagementFromRegistry();
const enableServerManagement = this.getEnableServerManagementFromRegistry();
if (enableServerManagement !== null) {
this.data.enableServerManagement = enableServerManagement;
}
@@ -59,7 +59,7 @@ export default class RegistryConfig extends EventEmitter {
// extract EnableAutoUpdater from the registry
try {
const enableAutoUpdater = await this.getEnableAutoUpdatorFromRegistry();
const enableAutoUpdater = this.getEnableAutoUpdatorFromRegistry();
if (enableAutoUpdater !== null) {
this.data.enableAutoUpdater = enableAutoUpdater;
}
@@ -76,13 +76,13 @@ export default class RegistryConfig extends EventEmitter {
/**
* Extracts a list of servers
*/
async getServersListFromRegistry() {
const defaultServers = await this.getRegistryEntry(`${BASE_REGISTRY_KEY_PATH}\\DefaultServerList`);
private getServersListFromRegistry() {
const defaultServers = this.getRegistryEntry(`${BASE_REGISTRY_KEY_PATH}\\DefaultServerList`);
return defaultServers.flat(2).reduce((servers: Server[], server) => {
if (server) {
servers.push({
name: (server as WindowsRegistry.RegistryItem).name,
url: (server as WindowsRegistry.RegistryItem).value,
name: (server as RegistryValue).name,
url: (server as RegistryValue).data as string,
});
}
return servers;
@@ -92,8 +92,8 @@ export default class RegistryConfig extends EventEmitter {
/**
* Determines whether server management has been enabled, disabled or isn't configured
*/
async getEnableServerManagementFromRegistry() {
const entries = (await this.getRegistryEntry(BASE_REGISTRY_KEY_PATH, 'EnableServerManagement'));
private getEnableServerManagementFromRegistry() {
const entries = this.getRegistryEntry(BASE_REGISTRY_KEY_PATH, 'EnableServerManagement');
const entry = entries.pop();
return entry ? entry === '0x1' : null;
}
@@ -101,8 +101,8 @@ export default class RegistryConfig extends EventEmitter {
/**
* Determines whether the auto updated has been enabled, disabled or isn't configured
*/
async getEnableAutoUpdatorFromRegistry() {
const entries = (await this.getRegistryEntry(BASE_REGISTRY_KEY_PATH, 'EnableAutoUpdater'));
private getEnableAutoUpdatorFromRegistry() {
const entries = this.getRegistryEntry(BASE_REGISTRY_KEY_PATH, 'EnableAutoUpdater');
const entry = entries.pop();
return entry ? entry === '0x1' : null;
}
@@ -113,13 +113,12 @@ export default class RegistryConfig extends EventEmitter {
* @param {string} key Path to the registry key to return
* @param {string} name Name of specific entry in the registry key to retrieve (optional)
*/
async getRegistryEntry(key: string, name?: string) {
private getRegistryEntry(key: string, name?: string) {
const results = [];
for (const hive of REGISTRY_HIVE_LIST) {
results.push(this.getRegistryEntryValues(hive, key, name));
}
const entryValues = await Promise.all(results);
return entryValues.filter((value) => value);
return results.filter((value) => value);
}
/**
@@ -128,54 +127,22 @@ export default class RegistryConfig extends EventEmitter {
* @param {WindowsRegistry} regKey A configured instance of the WindowsRegistry class
* @param {string} name Name of the specific entry to retrieve (optional)
*/
getRegistryEntryValues(hive: string, key: string, name?: string, utf8 = true) {
return new Promise<string | WindowsRegistry.RegistryItem[] | undefined>((resolve, reject) => {
try {
const registry = this.createRegistry(hive, key, utf8);
registry.values((error: Error, results: WindowsRegistry.RegistryItem[]) => {
if (error) {
this.handleRegistryEntryError(error, hive, key, name, utf8).then((result) => {
resolve(result);
});
return;
}
if (!results || results.length === 0) {
resolve(undefined);
return;
}
if (name) { // looking for a single entry value
const registryItem = results.find((item) => item.name === name);
resolve(registryItem && registryItem.value ? registryItem.value : undefined);
} else { // looking for an entry list
resolve(results);
}
});
} catch (e) {
this.handleRegistryEntryError(e as Error, hive, key, name, utf8).then((result) => {
if (result) {
resolve(result);
}
reject(e);
});
private getRegistryEntryValues(hive: HKEY, key: string, name?: string) {
try {
const results = enumerateValues(hive, key);
if (!results || results.length === 0) {
return undefined;
}
if (name) { // looking for a single entry value
const registryItem = results.find((item) => item.name === name);
return registryItem && registryItem.data ? registryItem.data : undefined;
}
});
}
handleRegistryEntryError(e: Error, hive: string, key: string, name?: string, utf8?: boolean) {
log.debug('There was an error accessing the registry for', {hive, key, name, utf8}, e);
if (utf8) {
log.debug('Trying without UTF-8...', {hive, key, name});
return this.getRegistryEntryValues(hive, key, name, false);
// looking for an entry list
return results;
} catch (e) {
log.debug('There was an error accessing the registry for', {hive, key, name}, e);
return undefined;
}
return Promise.resolve(undefined);
}
createRegistry(hive: string, key: string, utf8 = true) {
if (utf8) {
return new WindowsRegistryUTF8({hive, key, utf8});
}
return new WindowsRegistry({hive, key});
}
}

View File

@@ -250,7 +250,6 @@ function initializeBeforeAppReady() {
if (process.platform === 'darwin' || process.platform === 'win32') {
nativeTheme.on('updated', handleUpdateTheme);
handleUpdateTheme();
}
protocol.registerSchemesAsPrivileged([
@@ -389,6 +388,7 @@ async function initializeAfterAppReady() {
catch((err) => log.error('An error occurred: ', err));
}
handleUpdateTheme();
MainWindow.show();
let deeplinkingURL;

View File

@@ -1,34 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
declare module 'winreg-utf8' {
import WindowsRegistry from 'winreg';
export = WindowsRegistry;
}
declare namespace Winreg {
export interface Options {
/**
* Optional hostname, must start with '\\' sequence.
*/
host?: string;
/**
* Optional hive ID, default is HKLM.
*/
hive?: string;
/**
* Optional key, default is the root key.
*/
key?: string;
/**
* Optional registry hive architecture ('x86' or 'x64'; only valid on Windows 64 Bit Operating Systems).
*/
arch?: string;
utf8?: boolean;
}
}