summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2021-03-24 16:46:35 +0100
committerOskar Nyberg <oskar@mullvad.net>2021-03-24 16:46:35 +0100
commite666c4ac81b1e69f063aa226ed3cfc3bdb7efcf8 (patch)
tree2c1da7073862407291d14acc1c1322f1d207bd1c
parentc2cc25249101325dfc196c670b63391b273beaec (diff)
parent4437de21ab18a3ede2d067ce90db3c31d0865d82 (diff)
downloadmullvadvpn-e666c4ac81b1e69f063aa226ed3cfc3bdb7efcf8.tar.xz
mullvadvpn-e666c4ac81b1e69f063aa226ed3cfc3bdb7efcf8.zip
Merge branch 'disable-unused-functionality-in-renderer'
-rw-r--r--gui/src/config.json4
-rw-r--r--gui/src/main/index.ts112
-rw-r--r--gui/src/renderer/app.tsx12
-rw-r--r--gui/src/renderer/components/ErrorBoundary.tsx4
-rw-r--r--gui/src/renderer/components/Support.tsx18
-rw-r--r--gui/src/renderer/containers/SupportPage.tsx4
-rw-r--r--gui/src/shared/ipc-schema.ts4
7 files changed, 107 insertions, 51 deletions
diff --git a/gui/src/config.json b/gui/src/config.json
index 456da1ad02..59a214b604 100644
--- a/gui/src/config.json
+++ b/gui/src/config.json
@@ -1,11 +1,11 @@
{
+ "supportEmail": "support@mullvad.net",
"links": {
"purchase": "https://mullvad.net/account/",
"manageKeys": "https://mullvad.net/account/ports/",
"faq": "https://mullvad.net/help/tag/mullvad-app/",
"download": "https://mullvad.net/download/",
- "betaDownload": "https://mullvad.net/download/beta",
- "supportEmail": "support@mullvad.net"
+ "betaDownload": "https://mullvad.net/download/beta"
},
"colors": {
"darkBlue": "rgb(25, 46, 69)",
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index 5d08689036..efbf9a4aa3 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -14,6 +14,7 @@ import moment from 'moment';
import * as path from 'path';
import { sprintf } from 'sprintf-js';
import * as uuid from 'uuid';
+import config from '../config.json';
import { hasExpired } from '../shared/account-expiry';
import BridgeSettingsBuilder from '../shared/bridge-settings-builder';
import {
@@ -385,7 +386,12 @@ class ApplicationMain {
// fetching. https://github.com/electron/electron/issues/22995
session.defaultSession.setSpellCheckerDictionaryDownloadURL('https://00.00/');
+ // Blocks scripts in the renderer process from asking for any permission.
+ this.blockPermissionRequests();
+ // Blocks any http(s) and file requests that aren't supposed to happen.
this.blockRequests();
+ // Blocks navigation since it's not needed.
+ this.blockNavigation();
this.translations = this.updateCurrentLocale();
@@ -1138,7 +1144,8 @@ class ApplicationMain {
});
IpcMainEventChannel.problemReport.handleCollectLogs((toRedact) => {
- const reportPath = path.join(app.getPath('temp'), uuid.v4() + '.log');
+ const id = uuid.v4();
+ const reportPath = this.getProblemReportPath(id);
const executable = resolveBin('mullvad-problem-report');
const args = ['collect', '--output', reportPath];
if (toRedact.length > 0) {
@@ -1156,15 +1163,16 @@ class ApplicationMain {
reject(error.message);
} else {
log.debug(`Problem report was written to ${reportPath}`);
- resolve(reportPath);
+ resolve(id);
}
});
});
});
- IpcMainEventChannel.problemReport.handleSendReport(({ email, message, savedReport }) => {
+ IpcMainEventChannel.problemReport.handleSendReport(({ email, message, savedReportId }) => {
const executable = resolveBin('mullvad-problem-report');
- const args = ['send', '--email', email, '--message', message, '--report', savedReport];
+ const reportPath = this.getProblemReportPath(savedReportId);
+ const args = ['send', '--email', email, '--message', message, '--report', reportPath];
return new Promise((resolve, reject) => {
execFile(executable, args, { windowsHide: true }, (error, stdout, stderr) => {
@@ -1183,9 +1191,16 @@ class ApplicationMain {
});
});
+ IpcMainEventChannel.problemReport.handleViewLog((savedReportId) =>
+ shell.openPath(this.getProblemReportPath(savedReportId)),
+ );
+
IpcMainEventChannel.app.handleQuit(() => app.quit());
- IpcMainEventChannel.app.handleOpenUrl((url) => shell.openExternal(url));
- IpcMainEventChannel.app.handleOpenPath((path) => shell.openPath(path));
+ IpcMainEventChannel.app.handleOpenUrl(async (url) => {
+ if (Object.values(config.links).find((link) => url.startsWith(link))) {
+ await shell.openExternal(url);
+ }
+ });
IpcMainEventChannel.app.handleShowOpenDialog((options) => dialog.showOpenDialog(options));
}
@@ -1399,37 +1414,74 @@ class ApplicationMain {
};
}
+ private blockPermissionRequests() {
+ session.defaultSession.setPermissionRequestHandler((_webContents, _permission, callback) => {
+ callback(false);
+ });
+ session.defaultSession.setPermissionCheckHandler(() => false);
+ }
+
// Since the app frontend never performs any network requests, all requests originating from the
// renderer process are blocked to protect against the potential threat of malicious third party
// dependencies. There are a few exceptions which are described further down.
private blockRequests() {
- session.defaultSession.webRequest.onBeforeRequest(
- { urls: ['*://*/*'] },
- (details, callback) => {
- if (
- process.env.NODE_ENV === 'development' &&
- // Local web server providing assests (index.html, index.js and css files)
- (details.url.startsWith('http://localhost:8080/') ||
- // Automatic reloading performed by the browser-sync module
- details.url.startsWith('http://localhost:35829/browser-sync/') ||
- // Downloading of React and Redux developer tools.
- details.url.startsWith('https://clients2.google.com') ||
- details.url.startsWith('https://clients2.googleusercontent.com'))
- ) {
- callback({});
- } else {
- log.error(`${details.method} request blocked: ${details.url}`);
- callback({ cancel: true });
+ session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
+ if (this.allowFileAccess(details.url) || this.allowDevelopmentRequest(details.url)) {
+ callback({});
+ } else {
+ log.error(`${details.method} request blocked: ${details.url}`);
+ callback({ cancel: true });
- // Throw error in development to notify since this should never happen.
- if (process.env.NODE_ENV === 'development') {
- throw new Error('Web request blocked');
- }
+ // Throw error in development to notify since this should never happen.
+ if (process.env.NODE_ENV === 'development') {
+ throw new Error('Web request blocked');
}
- },
+ }
+ });
+ }
+
+ private allowFileAccess(url: string): boolean {
+ const buildDir = path.normalize(path.join(path.resolve(__dirname), '..', '..'));
+
+ if (url.startsWith('file:')) {
+ // Extract the path from the URL
+ let filePath = decodeURI(new URL(url).pathname);
+ if (process.platform === 'win32') {
+ // Windows paths shouldn't start with a '/'
+ filePath = filePath.replace(/^\//, '');
+ }
+ filePath = path.resolve(filePath);
+
+ return !path.relative(buildDir, filePath).includes('..');
+ } else {
+ return false;
+ }
+ }
+
+ private allowDevelopmentRequest(url: string): boolean {
+ return (
+ process.env.NODE_ENV === 'development' &&
+ // Local web server providing assests (index.html, index.js and css files)
+ (url.startsWith('http://localhost:8080/') ||
+ // Automatic reloading performed by the browser-sync module
+ url.startsWith('ws://localhost:35829/browser-sync') ||
+ url.startsWith('http://localhost:35829/browser-sync/') ||
+ // Downloading of React and Redux developer tools.
+ url.startsWith('devtools://devtools/') ||
+ url.startsWith('chrome-extension://') ||
+ url.startsWith('https://clients2.google.com') ||
+ url.startsWith('https://clients2.googleusercontent.com'))
);
}
+ private blockNavigation() {
+ app.on('web-contents-created', (_event, contents) => {
+ contents.on('will-navigate', (event) => {
+ event.preventDefault();
+ });
+ });
+ }
+
private async installDevTools() {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const installer = require('electron-devtools-installer');
@@ -1773,6 +1825,10 @@ class ApplicationMain {
return shell.openExternal(url);
}
}
+
+ private getProblemReportPath(id: string): string {
+ return path.join(app.getPath('temp'), `${id}.log`);
+ }
}
const applicationMain = new ApplicationMain();
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index 1277db2e63..6f656c5dfe 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -460,9 +460,13 @@ export default class AppRenderer {
public async sendProblemReport(
email: string,
message: string,
- savedReport: string,
+ savedReportId: string,
): Promise<void> {
- await IpcRendererEventChannel.problemReport.sendReport({ email, message, savedReport });
+ await IpcRendererEventChannel.problemReport.sendReport({ email, message, savedReportId });
+ }
+
+ public viewLog(id: string): Promise<string> {
+ return IpcRendererEventChannel.problemReport.viewLog(id);
}
public quit(): void {
@@ -473,10 +477,6 @@ export default class AppRenderer {
return IpcRendererEventChannel.app.openUrl(url);
}
- public openPath(path: string): Promise<string> {
- return IpcRendererEventChannel.app.openPath(path);
- }
-
public showOpenDialog(
options: Electron.OpenDialogOptions,
): Promise<Electron.OpenDialogReturnValue> {
diff --git a/gui/src/renderer/components/ErrorBoundary.tsx b/gui/src/renderer/components/ErrorBoundary.tsx
index 040ff71826..82e2910cc0 100644
--- a/gui/src/renderer/components/ErrorBoundary.tsx
+++ b/gui/src/renderer/components/ErrorBoundary.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import styled from 'styled-components';
-import { colors, links } from '../../config.json';
+import { colors, supportEmail } from '../../config.json';
import { messages } from '../../shared/gettext';
import log from '../../shared/logging';
import PlatformWindowContainer from '../containers/PlatformWindowContainer';
@@ -71,7 +71,7 @@ export default class ErrorBoundary extends React.Component<IProps, IState> {
messages
.pgettext('error-boundary-view', 'Something went wrong. Please contact us at %(email)s')
.split('%(email)s', 2);
- reachBackMessage.splice(1, 0, <Email>{links.supportEmail}</Email>);
+ reachBackMessage.splice(1, 0, <Email>{supportEmail}</Email>);
return (
<PlatformWindowContainer>
diff --git a/gui/src/renderer/components/Support.tsx b/gui/src/renderer/components/Support.tsx
index d6657d4c44..e7960a30c7 100644
--- a/gui/src/renderer/components/Support.tsx
+++ b/gui/src/renderer/components/Support.tsx
@@ -40,7 +40,7 @@ enum SendState {
interface ISupportState {
email: string;
message: string;
- savedReport?: string;
+ savedReportId?: string;
sendState: SendState;
disableActions: boolean;
showOutdatedVersionWarning: boolean;
@@ -56,7 +56,7 @@ interface ISupportProps {
saveReportForm: (form: ISupportReportForm) => void;
clearReportForm: () => void;
collectProblemReport: (accountsToRedact: string[]) => Promise<string>;
- sendProblemReport: (email: string, message: string, savedReport: string) => Promise<void>;
+ sendProblemReport: (email: string, message: string, savedReportId: string) => Promise<void>;
outdatedVersion: boolean;
suggestedIsBeta: boolean;
onExternalLink: (url: string) => void;
@@ -66,7 +66,7 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
public state = {
email: '',
message: '',
- savedReport: undefined,
+ savedReportId: undefined,
sendState: SendState.initial,
disableActions: false,
showOutdatedVersionWarning: false,
@@ -102,8 +102,8 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
public onViewLog = () => {
this.performWithActionsDisabled(async () => {
try {
- const reportPath = await this.collectLog();
- this.props.viewLog(reportPath);
+ const reportId = await this.collectLog();
+ this.props.viewLog(reportId);
} catch (error) {
// TODO: handle error
}
@@ -199,9 +199,9 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
this.collectLogPromise = collectPromise;
try {
- const reportPath = await collectPromise;
+ const reportId = await collectPromise;
return new Promise((resolve) => {
- this.setState({ savedReport: reportPath }, () => resolve(reportPath));
+ this.setState({ savedReportId: reportId }, () => resolve(reportId));
});
} catch (error) {
this.collectLogPromise = undefined;
@@ -216,8 +216,8 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
this.setState({ sendState: SendState.sending }, async () => {
try {
const { email, message } = this.state;
- const reportPath = await this.collectLog();
- await this.props.sendProblemReport(email, message, reportPath);
+ const reportId = await this.collectLog();
+ await this.props.sendProblemReport(email, message, reportId);
this.props.clearReportForm();
this.setState({ sendState: SendState.success }, () => {
resolve();
diff --git a/gui/src/renderer/containers/SupportPage.tsx b/gui/src/renderer/containers/SupportPage.tsx
index 94053f1ccd..1df4827223 100644
--- a/gui/src/renderer/containers/SupportPage.tsx
+++ b/gui/src/renderer/containers/SupportPage.tsx
@@ -23,8 +23,8 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props: IAppContext & RouteC
onClose() {
props.history.goBack();
},
- viewLog(path: string) {
- consumePromise(props.app.openPath(path));
+ viewLog(id: string) {
+ consumePromise(props.app.viewLog(id));
},
saveReportForm,
clearReportForm,
diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts
index 3f0d86d995..5cc347e51e 100644
--- a/gui/src/shared/ipc-schema.ts
+++ b/gui/src/shared/ipc-schema.ts
@@ -126,7 +126,6 @@ export const ipcSchema = {
app: {
quit: send<void>(),
openUrl: invoke<string, void>(),
- openPath: invoke<string, string>(),
showOpenDialog: invoke<Electron.OpenDialogOptions, Electron.OpenDialogReturnValue>(),
},
tunnel: {
@@ -185,7 +184,8 @@ export const ipcSchema = {
},
problemReport: {
collectLogs: invoke<string[], string>(),
- sendReport: invoke<{ email: string; message: string; savedReport: string }, void>(),
+ sendReport: invoke<{ email: string; message: string; savedReportId: string }, void>(),
+ viewLog: invoke<string, string>(),
},
logging: {
log: send<ILogEntry>(),