Enables sandbox on about window

This commit is contained in:
Josh Perez 2023-03-14 11:55:31 -04:00 committed by GitHub
parent 58691b2f5e
commit 4591b56f7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 262 additions and 93 deletions

View file

@ -28,3 +28,5 @@ sticker-creator/**/*.js
.eslintrc.js
webpack.config.ts
preload.bundle.*
about.browser.bundle.*
about.preload.bundle.*

2
.gitignore vendored
View file

@ -26,6 +26,8 @@ libtextsecure/components.js
stylesheets/*.css
/storybook-static/
preload.bundle.*
about.browser.bundle.*
about.preload.bundle.*
ts/sql/mainWorker.bundle.js.LICENSE.txt
# React / TypeScript

View file

@ -43,3 +43,5 @@ js/WebAudioRecorderMp3.js
stylesheets/_intlTelInput.scss
preload.bundle.*
about.browser.bundle.*
about.preload.bundle.*

View file

@ -30,5 +30,9 @@
<body>
<div id="app"></div>
<script type="application/javascript" src="ts/windows/init.js"></script>
<script
type="application/javascript"
src="about.browser.bundle.js"
></script>
</body>
</html>

View file

@ -1253,7 +1253,7 @@ async function showAbout() {
nodeIntegrationInWorker: false,
sandbox: false,
contextIsolation: true,
preload: join(__dirname, '../ts/windows/about/preload.js'),
preload: join(__dirname, '../about.preload.bundle.js'),
nativeWindowOpen: true,
},
};
@ -2357,6 +2357,11 @@ ipc.on('locale-data', event => {
event.returnValue = getResolvedMessagesLocale().messages;
});
ipc.on('getHasCustomTitleBar', event => {
// eslint-disable-next-line no-param-reassign
event.returnValue = OS.hasCustomTitleBar();
});
ipc.on('user-config-key', event => {
// eslint-disable-next-line no-param-reassign
event.returnValue = userConfig.get('key');

View file

@ -462,6 +462,8 @@
"app/*",
"preload.bundle.js",
"preload_utils.js",
"about.preload.bundle.js",
"about.browser.bundle.js",
"main.js",
"images/**",
"fonts/**",

View file

@ -98,3 +98,18 @@ main().catch(error => {
console.error(error.stack);
process.exit(1);
});
// About bundle
esbuild.build({
...bundleDefaults,
mainFields: ['browser', 'main'],
entryPoints: [path.join(ROOT_DIR, 'ts', 'windows', 'about', 'app.tsx')],
outfile: path.join(ROOT_DIR, 'about.browser.bundle.js'),
});
esbuild.build({
...bundleDefaults,
mainFields: ['browser', 'main'],
entryPoints: [path.join(ROOT_DIR, 'ts', 'windows', 'about', 'preload.ts')],
outfile: path.join(ROOT_DIR, 'about.preload.bundle.js'),
});

View file

@ -148,7 +148,7 @@ import { deleteAllLogs } from './util/deleteAllLogs';
import { ToastCaptchaFailed } from './components/ToastCaptchaFailed';
import { ToastCaptchaSolved } from './components/ToastCaptchaSolved';
import { showToast } from './util/showToast';
import { startInteractionMode } from './windows/startInteractionMode';
import { startInteractionMode } from './services/InteractionMode';
import type { MainWindowStatsType } from './windows/context';
import { ReactionSource } from './reactions/ReactionSource';
import { singleProtoJobQueue } from './jobs/singleProtoJobQueue';

View file

@ -3,28 +3,28 @@
import React from 'react';
import type { ExecuteMenuRoleType } from './TitleBarContainer';
import type { LocalizerType } from '../types/Util';
import { TitleBarContainer } from './TitleBarContainer';
import { useEscapeHandling } from '../hooks/useEscapeHandling';
import { useTheme } from '../hooks/useTheme';
import { TitleBarContainer } from './TitleBarContainer';
import type { ExecuteMenuRoleType } from './TitleBarContainer';
export type PropsType = {
closeAbout: () => unknown;
environment: string;
executeMenuRole: ExecuteMenuRoleType;
hasCustomTitleBar: boolean;
i18n: LocalizerType;
version: string;
hasCustomTitleBar: boolean;
executeMenuRole: ExecuteMenuRoleType;
};
export function About({
closeAbout,
i18n,
environment,
version,
hasCustomTitleBar,
executeMenuRole,
hasCustomTitleBar,
i18n,
version,
}: PropsType): JSX.Element {
useEscapeHandling(closeAbout);

View file

@ -10,6 +10,7 @@ import type { Theme } from '../util/theme';
import { themeClassName } from '../util/theme';
import { refMerger } from '../util/refMerger';
import { offsetDistanceModifier } from '../util/popperUtil';
import { getInteractionMode } from '../services/InteractionMode';
type EventWrapperPropsType = {
children: React.ReactNode;
@ -35,7 +36,7 @@ const TooltipEventWrapper = React.forwardRef<
}, [onHoverChanged]);
const onFocus = React.useCallback(() => {
if (window.getInteractionMode() === 'keyboard') {
if (getInteractionMode() === 'keyboard') {
on();
}
}, [on]);

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { getInteractionMode } from '../../services/InteractionMode';
export type PropsType = {
id: string;
@ -22,7 +23,7 @@ export class InlineNotificationWrapper extends React.Component<PropsType> {
};
public handleFocus = (): void => {
if (window.getInteractionMode() === 'keyboard') {
if (getInteractionMode() === 'keyboard') {
this.setSelected();
}
};

View file

@ -0,0 +1,12 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer } from 'electron';
import { getActiveWindowService } from '../services/ActiveWindowService';
const activeWindowService = getActiveWindowService(
window.document,
ipcRenderer
);
export { activeWindowService };

12
ts/context/config.ts Normal file
View file

@ -0,0 +1,12 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { RendererConfigType } from '../types/RendererConfig';
import { strictAssert } from '../util/assert';
const params = new URLSearchParams(document.location.search);
const configParam = params.get('config');
strictAssert(typeof configParam === 'string', 'config is not a string');
const config: RendererConfigType = JSON.parse(configParam);
export { config };

15
ts/context/environment.ts Normal file
View file

@ -0,0 +1,15 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { config } from './config';
import {
getEnvironment,
parseEnvironment,
setEnvironment,
} from '../environment';
setEnvironment(parseEnvironment(config.environment));
const environment = getEnvironment();
export { environment };

22
ts/context/i18n.ts Normal file
View file

@ -0,0 +1,22 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer } from 'electron';
import { config } from './config';
import { setupI18n } from '../util/setupI18n';
import { strictAssert } from '../util/assert';
const { resolvedTranslationsLocale } = config;
strictAssert(
resolvedTranslationsLocale,
'locale could not be parsed from config'
);
strictAssert(
typeof resolvedTranslationsLocale === 'string',
'locale is not a string'
);
const localeMessages = ipcRenderer.sendSync('locale-data');
const i18n = setupI18n(resolvedTranslationsLocale, localeMessages);
export { i18n };

View file

@ -16,7 +16,7 @@ const ACTIVE_EVENTS = [
'wheel',
];
export class ActiveWindowService {
class ActiveWindowService {
// This starting value might be wrong but we should get an update from the main process
// soon. We'd rather report that the window is inactive so we can show notifications.
private isInitialized = false;
@ -113,3 +113,37 @@ export class ActiveWindowService {
}
}
}
export type ActiveWindowServiceType = {
isActive(): boolean;
registerForActive(callback: () => void): void;
unregisterForActive(callback: () => void): void;
registerForChange(callback: (isActive: boolean) => void): void;
unregisterForChange(callback: (isActive: boolean) => void): void;
};
export function getActiveWindowService(
document: EventTarget,
ipc: NodeJS.EventEmitter
): ActiveWindowServiceType {
const activeWindowService = new ActiveWindowService();
activeWindowService.initialize(document, ipc);
return {
isActive(): boolean {
return activeWindowService.isActive();
},
registerForActive(callback: () => void): void {
return activeWindowService.registerForActive(callback);
},
unregisterForActive(callback: () => void): void {
return activeWindowService.unregisterForActive(callback);
},
registerForChange(callback: (isActive: boolean) => void): void {
return activeWindowService.registerForChange(callback);
},
unregisterForChange(callback: (isActive: boolean) => void): void {
return activeWindowService.unregisterForChange(callback);
},
};
}

View file

@ -1,8 +1,10 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
type InteractionModeType = 'mouse' | 'keyboard';
let initialized = false;
let interactionMode: 'mouse' | 'keyboard' = 'mouse';
let interactionMode: InteractionModeType = 'mouse';
export function startInteractionMode(): void {
if (initialized) {
@ -66,6 +68,8 @@ export function startInteractionMode(): void {
);
document.addEventListener('wheel', window.enterMouseMode, true);
document.addEventListener('mousedown', window.enterMouseMode, true);
window.getInteractionMode = () => interactionMode;
}
export function getInteractionMode(): InteractionModeType {
return interactionMode;
}

View file

@ -36,6 +36,7 @@ import { UUIDKind } from '../types/UUID';
import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
import { getInitialState as stickers } from '../types/Stickers';
import { getThemeType } from '../util/getThemeType';
import { getInteractionMode } from '../services/InteractionMode';
export function getInitialState({
badges,
@ -137,7 +138,7 @@ export function getInitialState({
...user(),
attachmentsPath: window.BasePaths.attachments,
i18n: window.i18n,
interactionMode: window.getInteractionMode(),
interactionMode: getInteractionMode(),
isMainWindowFullScreen: mainWindowStats.isFullScreen,
isMainWindowMaximized: mainWindowStats.isMaximized,
localeMessages: window.SignalContext.localeMessages,

View file

@ -5,7 +5,7 @@ import { assert } from 'chai';
import * as sinon from 'sinon';
import { EventEmitter } from 'events';
import { ActiveWindowService } from '../../services/ActiveWindowService';
import { getActiveWindowService } from '../../services/ActiveWindowService';
describe('ActiveWindowService', () => {
const fakeIpcEvent = {};
@ -23,16 +23,17 @@ describe('ActiveWindowService', () => {
}
it('is inactive at the start', () => {
const service = new ActiveWindowService();
service.initialize(createFakeDocument(), new EventEmitter());
const service = getActiveWindowService(
createFakeDocument(),
new EventEmitter()
);
assert.isFalse(service.isActive());
});
it('becomes active after focusing', () => {
const fakeIpc = new EventEmitter();
const service = new ActiveWindowService();
service.initialize(createFakeDocument(), fakeIpc);
const service = getActiveWindowService(createFakeDocument(), fakeIpc);
fakeIpc.emit('set-window-focus', fakeIpcEvent, true);
@ -41,8 +42,7 @@ describe('ActiveWindowService', () => {
it('becomes inactive after 15 seconds without interaction', function test() {
const fakeIpc = new EventEmitter();
const service = new ActiveWindowService();
service.initialize(createFakeDocument(), fakeIpc);
const service = getActiveWindowService(createFakeDocument(), fakeIpc);
fakeIpc.emit('set-window-focus', fakeIpcEvent, true);
@ -61,8 +61,7 @@ describe('ActiveWindowService', () => {
it(`is inactive even in the face of ${eventName} events if unfocused`, function test() {
const fakeDocument = createFakeDocument();
const fakeIpc = new EventEmitter();
const service = new ActiveWindowService();
service.initialize(fakeDocument, fakeIpc);
const service = getActiveWindowService(fakeDocument, fakeIpc);
fakeIpc.emit('set-window-focus', fakeIpcEvent, false);
@ -73,8 +72,7 @@ describe('ActiveWindowService', () => {
it(`stays active if focused and receiving ${eventName} events`, function test() {
const fakeDocument = createFakeDocument();
const fakeIpc = new EventEmitter();
const service = new ActiveWindowService();
service.initialize(fakeDocument, fakeIpc);
const service = getActiveWindowService(fakeDocument, fakeIpc);
fakeIpc.emit('set-window-focus', fakeIpcEvent, true);
@ -94,8 +92,7 @@ describe('ActiveWindowService', () => {
it('calls callbacks when going from unfocused to focused', () => {
const fakeIpc = new EventEmitter();
const service = new ActiveWindowService();
service.initialize(createFakeDocument(), fakeIpc);
const service = getActiveWindowService(createFakeDocument(), fakeIpc);
const callback = sinon.stub();
service.registerForActive(callback);
@ -108,8 +105,7 @@ describe('ActiveWindowService', () => {
it('calls callbacks when receiving a click event after being focused', function test() {
const fakeDocument = createFakeDocument();
const fakeIpc = new EventEmitter();
const service = new ActiveWindowService();
service.initialize(fakeDocument, fakeIpc);
const service = getActiveWindowService(fakeDocument, fakeIpc);
fakeIpc.emit('set-window-focus', fakeIpcEvent, true);
@ -125,8 +121,7 @@ describe('ActiveWindowService', () => {
it('only calls callbacks every 5 seconds; it is throttled', function test() {
const fakeIpc = new EventEmitter();
const service = new ActiveWindowService();
service.initialize(createFakeDocument(), fakeIpc);
const service = getActiveWindowService(createFakeDocument(), fakeIpc);
const callback = sinon.stub();
service.registerForActive(callback);
@ -150,8 +145,7 @@ describe('ActiveWindowService', () => {
it('can remove callbacks', () => {
const fakeDocument = createFakeDocument();
const fakeIpc = new EventEmitter();
const service = new ActiveWindowService();
service.initialize(fakeDocument, fakeIpc);
const service = getActiveWindowService(fakeDocument, fakeIpc);
const callback = sinon.stub();
service.registerForActive(callback);

View file

@ -24,6 +24,8 @@ const excludedFilesRegexp = RegExp(
[
'^release/',
'^preload.bundle.js(LICENSE.txt|map)?',
'^about.browser.bundle.js(LICENSE.txt|map)?',
'^about.preload.bundle.js(LICENSE.txt|map)?',
'^storybook-static/',
// Non-distributed files

10
ts/window.d.ts vendored
View file

@ -3,6 +3,7 @@
// Captures the globals put in place by preload.js, background.js and others
import type { MenuItemConstructorOptions } from 'electron';
import type { Store } from 'redux';
import type * as Backbone from 'backbone';
import type PQueue from 'p-queue/dist';
@ -101,7 +102,16 @@ export type FeatureFlagType = {
GV2_MIGRATION_DISABLE_INVITE: boolean;
};
type AboutWindowType = {
environmentText: string;
executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise<void>;
hasCustomTitleBar: boolean;
i18n: LocalizerType;
version: string;
};
export type SignalCoreType = {
AboutWindow?: AboutWindowType;
Crypto: typeof Crypto;
Curve: typeof Curve;
Data: typeof Data;

24
ts/windows/about/app.tsx Normal file
View file

@ -0,0 +1,24 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { About } from '../../components/About';
import { strictAssert } from '../../util/assert';
const { AboutWindow } = window.Signal;
strictAssert(AboutWindow, 'window values not provided');
ReactDOM.render(
<About
closeAbout={() => AboutWindow.executeMenuRole('close')}
environment={AboutWindow.environmentText}
executeMenuRole={AboutWindow.executeMenuRole}
hasCustomTitleBar={AboutWindow.hasCustomTitleBar}
i18n={AboutWindow.i18n}
version={AboutWindow.version}
/>,
document.getElementById('app')
);

View file

@ -1,42 +1,63 @@
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import ReactDOM from 'react-dom';
import { contextBridge } from 'electron';
import type { MenuItemConstructorOptions } from 'electron';
import { contextBridge, ipcRenderer } from 'electron';
import { activeWindowService } from '../../context/activeWindowService';
import { config } from '../../context/config';
import { createNativeThemeListener } from '../../context/createNativeThemeListener';
import { createSetting } from '../../util/preload';
import { environment } from '../../context/environment';
import { getClassName } from '../../OS';
import { i18n } from '../../context/i18n';
import { waitForSettingsChange } from '../../context/waitForSettingsChange';
import { SignalContext } from '../context';
import { About } from '../../components/About';
async function executeMenuRole(
role: MenuItemConstructorOptions['role']
): Promise<void> {
await ipcRenderer.invoke('executeMenuRole', role);
}
contextBridge.exposeInMainWorld('SignalContext', {
...SignalContext,
renderWindow: () => {
const environmentText: Array<string> = [SignalContext.getEnvironment()];
const environments: Array<string> = [environment];
const appInstance = SignalContext.getAppInstance();
if (appInstance) {
environmentText.push(appInstance);
}
if (config.appInstance) {
environments.push(String(config.appInstance));
}
let platform = '';
if (process.platform === 'darwin') {
if (process.arch === 'arm64') {
platform = ` (${SignalContext.i18n('appleSilicon')})`;
} else {
platform = ' (Intel)';
}
}
let platform = '';
if (process.platform === 'darwin') {
if (process.arch === 'arm64') {
platform = ` (${i18n('appleSilicon')})`;
} else {
platform = ' (Intel)';
}
}
ReactDOM.render(
React.createElement(About, {
closeAbout: () => SignalContext.executeMenuRole('close'),
environment: `${environmentText.join(' - ')}${platform}`,
i18n: SignalContext.i18n,
version: SignalContext.getVersion(),
hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(),
executeMenuRole: SignalContext.executeMenuRole,
}),
document.getElementById('app')
);
const environmentText = `${environments.join(' - ')}${platform}`;
const hasCustomTitleBar = ipcRenderer.sendSync('getHasCustomTitleBar');
const Signal = {
AboutWindow: {
environmentText,
executeMenuRole,
hasCustomTitleBar,
i18n,
version: String(config.version),
},
});
};
contextBridge.exposeInMainWorld('Signal', Signal);
// TODO DESKTOP-5054
const SignalContext = {
activeWindowService,
OS: {
getClassName,
hasCustomTitleBar: () => hasCustomTitleBar,
},
Settings: {
themeSetting: createSetting('themeSetting', { setter: false }),
waitForChange: waitForSettingsChange,
},
nativeThemeListener: createNativeThemeListener(ipcRenderer, window),
};
contextBridge.exposeInMainWorld('SignalContext', SignalContext);

View file

@ -12,13 +12,15 @@ import type { LocaleMessagesType } from '../types/I18N';
import type { NativeThemeType } from '../context/createNativeThemeListener';
import type { SettingType } from '../util/preload';
import type { RendererConfigType } from '../types/RendererConfig';
import { ActiveWindowService } from '../services/ActiveWindowService';
import { Bytes } from '../context/Bytes';
import { Crypto } from '../context/Crypto';
import { Timers } from '../context/Timers';
import { setupI18n } from '../util/setupI18n';
import type { ActiveWindowServiceType } from '../services/ActiveWindowService';
import { config } from '../context/config';
import { i18n } from '../context/i18n';
import { activeWindowService } from '../context/activeWindowService';
import {
getEnvironment,
parseEnvironment,
@ -27,7 +29,7 @@ import {
import { strictAssert } from '../util/assert';
import { createSetting } from '../util/preload';
import { initialize as initializeLogging } from '../logging/set_up_renderer_logging';
import { waitForSettingsChange } from './waitForSettingsChange';
import { waitForSettingsChange } from '../context/waitForSettingsChange';
import { createNativeThemeListener } from '../context/createNativeThemeListener';
import {
isWindows,
@ -37,24 +39,6 @@ import {
getClassName,
} from '../OS';
const activeWindowService = new ActiveWindowService();
activeWindowService.initialize(window.document, ipcRenderer);
const params = new URLSearchParams(document.location.search);
const configParam = params.get('config');
strictAssert(typeof configParam === 'string', 'config is not a string');
const config: RendererConfigType = JSON.parse(configParam);
const { resolvedTranslationsLocale } = config;
strictAssert(
resolvedTranslationsLocale,
'locale could not be parsed from config'
);
strictAssert(
typeof resolvedTranslationsLocale === 'string',
'locale is not a string'
);
const localeMessages = ipcRenderer.sendSync('locale-data');
setEnvironment(parseEnvironment(config.environment));
@ -74,7 +58,7 @@ export type SignalContextType = {
nativeThemeListener: NativeThemeType;
setIsCallActive: (isCallActive: boolean) => unknown;
activeWindowService: typeof activeWindowService;
activeWindowService: ActiveWindowServiceType;
Settings: {
themeSetting: SettingType<IPCEventsValuesType['themeSetting']>;
waitForChange: () => Promise<void>;
@ -128,7 +112,7 @@ export const SignalContext: SignalContextType = {
getPath: (name: 'userData' | 'home'): string => {
return String(config[`${name}Path`]);
},
i18n: setupI18n(resolvedTranslationsLocale, localeMessages),
i18n,
localeMessages,
log: window.SignalContext.log,
nativeThemeListener: createNativeThemeListener(ipcRenderer, window),

View file

@ -8,6 +8,6 @@ if (window.SignalContext.OS.hasCustomTitleBar()) {
if (window.SignalContext.renderWindow) {
window.SignalContext.renderWindow();
} else {
} else if (window.SignalContext.log) {
window.SignalContext.log.error('renderWindow is undefined!');
}

View file

@ -16,7 +16,7 @@ import {
import { awaitObject } from '../../util/awaitObject';
import { DurationInSeconds } from '../../util/durations';
import { createSetting, createCallback } from '../../util/preload';
import { startInteractionMode } from '../startInteractionMode';
import { startInteractionMode } from '../../services/InteractionMode';
function doneRendering() {
ipcRenderer.send('settings-done-rendering');