[MM-54465] Add Permissions Manager, require manual interaction to approve use of microphone and camera, to send notifications, and to use location per server (#2832)

This commit is contained in:
Devin Binnie
2023-09-15 10:54:22 -04:00
committed by GitHub
parent 887dcd7b1f
commit b7e5c6091d
8 changed files with 411 additions and 123 deletions

View File

@@ -6,11 +6,9 @@ import path from 'path';
import {app, session} from 'electron';
import Config from 'common/config';
import {parseURL, isTrustedURL} from 'common/utils/url';
import parseArgs from 'main/ParseArgs';
import ViewManager from 'main/views/viewManager';
import MainWindow from 'main/windows/mainWindow';
import {initialize} from './initialize';
import {clearAppCache, getDeeplinkingURL, wasUpdated} from './utils';
@@ -107,11 +105,6 @@ jest.mock('common/config', () => ({
initRegistry: jest.fn(),
}));
jest.mock('common/utils/url', () => ({
parseURL: jest.fn(),
isTrustedURL: jest.fn(),
}));
jest.mock('main/allowProtocolDialog', () => ({
init: jest.fn(),
}));
@@ -169,9 +162,7 @@ jest.mock('main/UserActivityMonitor', () => ({
on: jest.fn(),
startMonitoring: jest.fn(),
}));
jest.mock('main/windows/callsWidgetWindow', () => ({
isCallsWidget: jest.fn(),
}));
jest.mock('main/windows/callsWidgetWindow', () => ({}));
jest.mock('main/views/viewManager', () => ({
getViewByWebContentsId: jest.fn(),
handleDeepLink: jest.fn(),
@@ -277,46 +268,5 @@ describe('main/app/initialize', () => {
expect(ViewManager.handleDeepLink).toHaveBeenCalledWith('mattermost://server-1.com');
});
it('should allow permission requests for supported types from trusted URLs', async () => {
ViewManager.getViewByWebContentsId.mockReturnValue({
view: {
server: {
url: new URL('http://server-1.com'),
},
},
});
parseURL.mockImplementation((url) => new URL(url));
isTrustedURL.mockImplementation((url) => url.toString() === 'http://server-1.com/');
let callback = jest.fn();
session.defaultSession.setPermissionRequestHandler.mockImplementation((cb) => {
cb({id: 1, getURL: () => 'http://server-1.com'}, 'bad-permission', callback);
});
await initialize();
expect(callback).toHaveBeenCalledWith(false);
callback = jest.fn();
MainWindow.get.mockReturnValue({webContents: {id: 1}});
session.defaultSession.setPermissionRequestHandler.mockImplementation((cb) => {
cb({id: 1, getURL: () => 'http://server-1.com'}, 'openExternal', callback);
});
await initialize();
expect(callback).toHaveBeenCalledWith(true);
callback = jest.fn();
session.defaultSession.setPermissionRequestHandler.mockImplementation((cb) => {
cb({id: 2, getURL: () => 'http://server-1.com'}, 'openExternal', callback);
});
await initialize();
expect(callback).toHaveBeenCalledWith(true);
callback = jest.fn();
session.defaultSession.setPermissionRequestHandler.mockImplementation((cb) => {
cb({id: 2, getURL: () => 'http://server-2.com'}, 'openExternal', callback);
});
await initialize();
expect(callback).toHaveBeenCalledWith(false);
});
});
});

View File

@@ -32,7 +32,6 @@ import {
TOGGLE_SECURE_INPUT,
} from 'common/communication';
import Config from 'common/config';
import {isTrustedURL, parseURL} from 'common/utils/url';
import {Logger} from 'common/log';
import AllowProtocolDialog from 'main/allowProtocolDialog';
@@ -47,12 +46,12 @@ import CriticalErrorHandler from 'main/CriticalErrorHandler';
import downloadsManager from 'main/downloadsManager';
import i18nManager from 'main/i18nManager';
import parseArgs from 'main/ParseArgs';
import PermissionsManager from 'main/permissionsManager';
import ServerManager from 'common/servers/serverManager';
import TrustedOriginsStore from 'main/trustedOrigins';
import Tray from 'main/tray/tray';
import UserActivityMonitor from 'main/UserActivityMonitor';
import ViewManager from 'main/views/viewManager';
import CallsWidgetWindow from 'main/windows/callsWidgetWindow';
import MainWindow from 'main/windows/mainWindow';
import {protocols} from '../../../electron-builder.json';
@@ -392,56 +391,9 @@ async function initializeAfterAppReady() {
ipcMain.emit('update-dict');
// supported permission types
const supportedPermissionTypes = [
'media',
'geolocation',
'notifications',
'fullscreen',
'openExternal',
'clipboard-sanitized-write',
];
// handle permission requests
// - approve if a supported permission type and the request comes from the renderer or one of the defined servers
defaultSession.setPermissionRequestHandler((webContents, permission, callback) => {
log.debug('permission requested', webContents.getURL(), permission);
// is the requested permission type supported?
if (!supportedPermissionTypes.includes(permission)) {
callback(false);
return;
}
// is the request coming from the renderer?
const mainWindow = MainWindow.get();
if (mainWindow && webContents.id === mainWindow.webContents.id) {
callback(true);
return;
}
if (CallsWidgetWindow.isCallsWidget(webContents.id)) {
callback(true);
return;
}
const requestingURL = webContents.getURL();
const serverURL = ViewManager.getViewByWebContentsId(webContents.id)?.view.server.url;
if (!serverURL) {
callback(false);
return;
}
const parsedURL = parseURL(requestingURL);
if (!parsedURL) {
callback(false);
return;
}
// is the requesting url trusted?
callback(isTrustedURL(parsedURL, serverURL));
});
defaultSession.setPermissionRequestHandler(PermissionsManager.handlePermissionRequest);
if (wasUpdated(AppVersionManager.lastAppVersion)) {
clearAppCache();