[MM-54742] Force secure keyboard entry off when window loses focus and when servers switch (#2869)

* [MM-54742] Force secure keyboard entry off when window loses focus and when servers switch

* Fix for when window is not focused, added tests
This commit is contained in:
Devin Binnie
2023-10-10 10:47:13 -04:00
committed by GitHub
parent 9aec7db821
commit 36daa3d67e
5 changed files with 64 additions and 5 deletions

View File

@@ -16,6 +16,7 @@ import {
SHOW_NEW_SERVER_MODAL, SHOW_NEW_SERVER_MODAL,
SHOW_REMOVE_SERVER_MODAL, SHOW_REMOVE_SERVER_MODAL,
SWITCH_SERVER, SWITCH_SERVER,
TOGGLE_SECURE_INPUT,
UPDATE_SERVER_ORDER, UPDATE_SERVER_ORDER,
UPDATE_SHORTCUT_MENU, UPDATE_SHORTCUT_MENU,
UPDATE_TAB_ORDER, UPDATE_TAB_ORDER,
@@ -87,6 +88,7 @@ export class ServerViewState {
ServerManager.getServerLog(serverId, 'WindowManager').error('Cannot find server in config'); ServerManager.getServerLog(serverId, 'WindowManager').error('Cannot find server in config');
return; return;
} }
ipcMain.emit(TOGGLE_SECURE_INPUT, null, false);
this.currentServerId = serverId; this.currentServerId = serverId;
const nextView = ServerManager.getLastActiveTabForServer(serverId); const nextView = ServerManager.getLastActiveTabForServer(serverId);
if (waitForViewToExist) { if (waitForViewToExist) {

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {app} from 'electron';
import {getLocalURLString, getLocalPreload} from 'main/utils'; import {getLocalURLString, getLocalPreload} from 'main/utils';
import ServerManager from 'common/servers/serverManager'; import ServerManager from 'common/servers/serverManager';
import MainWindow from 'main/windows/mainWindow'; import MainWindow from 'main/windows/mainWindow';
@@ -9,8 +11,15 @@ import ModalManager from 'main/views/modalManager';
import { import {
handleWelcomeScreenModal, handleWelcomeScreenModal,
handleMainWindowIsShown, handleMainWindowIsShown,
handleToggleSecureInput,
} from './intercom'; } from './intercom';
jest.mock('electron', () => ({
app: {
setSecureKeyboardEntryEnabled: jest.fn(),
},
}));
jest.mock('app/serverViewState', () => ({})); jest.mock('app/serverViewState', () => ({}));
jest.mock('common/config', () => ({ jest.mock('common/config', () => ({
setServers: jest.fn(), setServers: jest.fn(),
@@ -73,4 +82,38 @@ describe('main/app/intercom', () => {
expect(ModalManager.addModal).not.toHaveBeenCalled(); expect(ModalManager.addModal).not.toHaveBeenCalled();
}); });
}); });
describe('handleToggleSecureInput', () => {
beforeEach(() => {
MainWindow.get.mockReturnValue({
isFocused: () => true,
});
});
afterEach(() => {
jest.resetAllMocks();
});
it('should not fire for OSes that are not macOS', () => {
const originalPlatform = process.platform;
Object.defineProperty(process, 'platform', {
value: 'linux',
});
handleToggleSecureInput({}, true);
Object.defineProperty(process, 'platform', {
value: originalPlatform,
});
expect(app.setSecureKeyboardEntryEnabled).not.toHaveBeenCalled();
});
it('should not fire if window is not focused', () => {
MainWindow.get.mockReturnValue({isFocused: () => false});
handleToggleSecureInput({}, true);
expect(app.setSecureKeyboardEntryEnabled).not.toHaveBeenCalled();
});
});
}); });

View File

@@ -153,6 +153,11 @@ export function handleToggleSecureInput(event: IpcMainEvent, secureInput: boolea
return; return;
} }
// Don't allow this to turn on if the main window isn't focused
if (secureInput && !MainWindow.get()?.isFocused()) {
return;
}
// Enforce macOS to restrict processes from reading the keyboard input when in a password field // Enforce macOS to restrict processes from reading the keyboard input when in a password field
log.debug('handleToggleSecureInput', secureInput); log.debug('handleToggleSecureInput', secureInput);
app.setSecureKeyboardEntryEnabled(secureInput); app.setSecureKeyboardEntryEnabled(secureInput);

View File

@@ -376,14 +376,21 @@ window.addEventListener('resize', () => {
}); });
let isPasswordBox = false; let isPasswordBox = false;
const shouldSecureInput = (element, force = false) => {
window.addEventListener('focusin', (event) => { const targetIsPasswordBox = (element && element.tagName === 'INPUT' && element.type === 'password');
const targetIsPasswordBox = event.target.tagName === 'INPUT' && event.target.type === 'password'; if (targetIsPasswordBox && (!isPasswordBox || force)) {
if (targetIsPasswordBox && !isPasswordBox) {
ipcRenderer.send(TOGGLE_SECURE_INPUT, true); ipcRenderer.send(TOGGLE_SECURE_INPUT, true);
} else if (!targetIsPasswordBox && isPasswordBox) { } else if (!targetIsPasswordBox && (isPasswordBox || force)) {
ipcRenderer.send(TOGGLE_SECURE_INPUT, false); ipcRenderer.send(TOGGLE_SECURE_INPUT, false);
} }
isPasswordBox = targetIsPasswordBox; isPasswordBox = targetIsPasswordBox;
};
window.addEventListener('focusin', (event) => {
shouldSecureInput(event.target);
});
window.addEventListener('focus', () => {
shouldSecureInput(document.activeElement, true);
}); });

View File

@@ -27,6 +27,7 @@ import {
MAIN_WINDOW_RESIZED, MAIN_WINDOW_RESIZED,
MAIN_WINDOW_FOCUSED, MAIN_WINDOW_FOCUSED,
VIEW_FINISHED_RESIZING, VIEW_FINISHED_RESIZING,
TOGGLE_SECURE_INPUT,
} from 'common/communication'; } from 'common/communication';
import Config from 'common/config'; import Config from 'common/config';
import {Logger} from 'common/log'; import {Logger} from 'common/log';
@@ -314,6 +315,7 @@ export class MainWindow extends EventEmitter {
globalShortcut.unregisterAll(); globalShortcut.unregisterAll();
this.emit(MAIN_WINDOW_RESIZED, this.getBounds()); this.emit(MAIN_WINDOW_RESIZED, this.getBounds());
ipcMain.emit(TOGGLE_SECURE_INPUT, null, false);
// App should save bounds when a window is closed. // App should save bounds when a window is closed.
// However, 'close' is not fired in some situations(shutdown, ctrl+c) // However, 'close' is not fired in some situations(shutdown, ctrl+c)