Skip to content

Commit

Permalink
feat: Add authenticated proxy support (WEBAPP-6412) (backport) (#3131)
Browse files Browse the repository at this point in the history
  • Loading branch information
ffflorian authored Oct 25, 2019
1 parent c8b2834 commit 9cda28d
Show file tree
Hide file tree
Showing 9 changed files with 453 additions and 10 deletions.
102 changes: 102 additions & 0 deletions electron/css/proxy-prompt.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Wire
* Copyright (C) 2019 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

html {
width: 100%;
height: 100%;
}

body {
align-items: center;
background-color: #fff;
color: #34383b;
cursor: default;
display: flex;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Helvetica, Arial, sans-serif;
font-size: 14px;
height: 100%;
justify-content: center;
margin: 0;
padding: 0;
text-align: center;
width: 100%;
}

#container {
}

#form {
width: 100%;
}

h1 {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 300;
}

p {
margin: 0 0 16px;
}

label {
display: inline-block;
width: 80px;
text-align: right;
margin-right: 8px;
}

input {
padding: 0.5em;
border-radius: 4px;
border: 1px solid #bac8d1;
width: 160px;
font-size: 14px;
}

button {
cursor: pointer;
border-radius: 2px;
margin: 0 4px;
border: 0;
line-height: 1em;
padding: 8px 8px;
width: 80px;
font-size: 12px;
}
.copy {
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid #e1e7eb;
}

.buttons {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #e1e7eb;
}

#okButton {
background-color: #3879d9;
color: white;
}

#cancelButton {
background-color: #ddd;
color: black;
}
27 changes: 27 additions & 0 deletions electron/html/proxy-prompt.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<!-- Wire, Copyright (C) 2019 Wire Swiss GmbH -->
<html>
<head>
<link rel="stylesheet" href="../css/proxy-prompt.css" />
</head>
<body>
<div id="container" data-uie-name="proxy">
<form id="form">
<h1 data-string="proxyPromptTitle"></h1>
<p class="copy"><span data-string="proxyPromptHeadline"></span></p>
<p>
<label for="usernameInput" data-string="proxyPromptUsername" data-uie-name="input-username"></label>
<input type="text" id="usernameInput" />
</p>
<p>
<label for="passwordInput" data-string="proxyPromptPassword" data-uie-name="input-password"></label>
<input type="password" id="passwordInput" />
</p>
<p class="buttons">
<button id="cancelButton" data-string="proxyPromptCancel" data-uie-name="do-cancel" type="reset"></button>
<button id="okButton" data-string="proxyPromptOk" data-uie-name="do-ok" type="submit"></button>
</p>
</form>
</div>
</body>
</html>
6 changes: 6 additions & 0 deletions electron/src/interfaces/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ export type i18nLanguageIdentifier =
| 'menuVideoCall'
| 'menuView'
| 'menuWindow'
| 'proxyPromptCancel'
| 'proxyPromptHeadline'
| 'proxyPromptOk'
| 'proxyPromptPassword'
| 'proxyPromptTitle'
| 'proxyPromptUsername'
| 'restartLater'
| 'restartLocale'
| 'restartNeeded'
Expand Down
7 changes: 7 additions & 0 deletions electron/src/lib/eventType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ export const EVENT_TYPE = {
PREFERENCES: {
SHOW: 'EVENT_TYPE.PREFERENCES.SHOW',
},
PROXY_PROMPT: {
CANCELED: 'EVENT_TYPE.PROXY_PROMPT.CANCELED',
LOADED: 'EVENT_TYPE.PROXY_PROMPT.LOADED',
LOCALE_RENDER: 'EVENT_TYPE.PROXY_PROMPT.LOCALE_RENDER',
LOCALE_VALUES: 'EVENT_TYPE.PROXY_PROMPT.LOCALE_VALUES',
SUBMITTED: 'EVENT_TYPE.PROXY_PROMPT.AUTHENTICATION_DATA',
},
UI: {
BADGE_COUNT: 'EVENT_TYPE.UI.BADGE_COUNT',
SYSTEM_MENU: 'EVENT_TYPE.UI.SYSTEM_MENU',
Expand Down
96 changes: 94 additions & 2 deletions electron/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {BrowserWindow, Event, IpcMessageEvent, Menu, app, ipcMain, shell} from '
import WindowStateKeeper = require('electron-window-state');
import fileUrl = require('file-url');
import * as fs from 'fs-extra';
import {getProxySettings} from 'get-proxy-settings';
import * as logdown from 'logdown';
import * as minimist from 'minimist';
import * as path from 'path';
Expand Down Expand Up @@ -51,6 +52,7 @@ import {settings} from './settings/ConfigurationPersistence';
import {SettingsType} from './settings/SettingsType';
import {SingleSignOn} from './sso/SingleSignOn';
import {AboutWindow} from './window/AboutWindow';
import {ProxyPromptWindow} from './window/ProxyPromptWindow';
import {WindowManager} from './window/WindowManager';
import {WindowUtil} from './window/WindowUtil';

Expand All @@ -68,18 +70,32 @@ const WINDOW_SIZE = {
MIN_WIDTH: 760,
};

let authenticatedProxyInfo: URL | undefined;

const customProtocolHandler = new CustomProtocolHandler();

// Config
const argv = minimist(process.argv.slice(1));
const BASE_URL = EnvironmentUtil.web.getWebappUrl(argv.env);

const logger = getLogger(__filename);

if (argv.version) {
console.log(config.version);
process.exit();
}

const logger = getLogger(__filename);
if (argv['proxy-server-auth']) {
try {
authenticatedProxyInfo = new URL(argv['proxy-server-auth']);
if (authenticatedProxyInfo.origin === 'null') {
authenticatedProxyInfo = undefined;
throw new Error('No protocol for the proxy server specified.');
}
} catch (error) {
logger.error(`Could not parse authenticated proxy URL: "${error.message}"`);
}
}

// Icon
const ICON = `wire.${EnvironmentUtil.platform.IS_WINDOWS ? 'ico' : 'png'}`;
Expand All @@ -88,6 +104,7 @@ let tray: TrayHandler;

let isFullScreen = false;
let isQuitting = false;
let triedProxy = false;
let main: BrowserWindow;

Object.entries(config).forEach(([key, value]) => {
Expand Down Expand Up @@ -294,6 +311,63 @@ const handleAppEvents = () => {
isQuitting = true;
});

app.on('login', async (event, webContents, request, authInfo, callback) => {
if (authInfo.isProxy) {
event.preventDefault();

if (authenticatedProxyInfo) {
const {username, password} = authenticatedProxyInfo;
logger.info('Sending provided credentials to authenticated proxy ...');
return callback(username, password);
}

const systemProxy = await getProxySettings();
const systemProxySettings = systemProxy && (systemProxy.http || systemProxy.https);

if (systemProxySettings) {
const {
credentials: {username, password},
protocol,
} = systemProxySettings;
authenticatedProxyInfo = new URL(`${protocol}//${username}:${password}@${authInfo.host}`);
return callback(username, password);
}

ipcMain.once(
EVENT_TYPE.PROXY_PROMPT.SUBMITTED,
(event: IpcMessageEvent, promptData: {password: string; username: string}) => {
const {username, password} = promptData;

logger.log('Proxy prompt was submitted');
const [originalProxyValue] = argv['proxy-server'] || argv['proxy-server-auth'] || ['http://'];
const protocol = /^[^:]+:\/\//.exec(originalProxyValue);
authenticatedProxyInfo = new URL(`${protocol}${username}:${password}@${authInfo.host}`);
callback(username, password);
},
);

ipcMain.once(EVENT_TYPE.PROXY_PROMPT.CANCELED, async () => {
logger.log('Proxy prompt was canceled');
webContents.session.setProxy(
{
pacScript: '',
proxyBypassRules: '',
proxyRules: '',
},
() => {
callback('', '');
main.reload();
},
);
});

if (!triedProxy) {
ProxyPromptWindow.showWindow();
triedProxy = true;
}
}
});

// System Menu, Tray Icon & Show window
app.on('ready', () => {
const mainWindowState = initWindowStateKeeper();
Expand Down Expand Up @@ -410,9 +484,27 @@ class ElectronWrapperInit {
}
};

app.on('web-contents-created', (webviewEvent: Electron.Event, contents: Electron.WebContents) => {
app.on('web-contents-created', async (webviewEvent: Electron.Event, contents: Electron.WebContents) => {
WebViewFocus.bindTracker(webviewEvent, contents);

if (authenticatedProxyInfo && authenticatedProxyInfo.origin && contents.session) {
const proxyURL = `${authenticatedProxyInfo.protocol}//${authenticatedProxyInfo.origin}`;
logger.info(`Setting proxy to URL "${proxyURL}" ...`);

contents.session.allowNTLMCredentialsForDomains(authenticatedProxyInfo.hostname);

await new Promise(resolve =>
contents.session.setProxy(
{
pacScript: '',
proxyBypassRules: '',
proxyRules: proxyURL,
},
() => resolve(),
),
);
}

switch (contents.getType()) {
case 'window': {
contents.on('will-attach-webview', (event, webPreferences, params) => {
Expand Down
77 changes: 77 additions & 0 deletions electron/src/renderer/menu/preload-proxy-prompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Wire
* Copyright (C) 2019 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {IpcMessageEvent, ipcRenderer} from 'electron';

import {EVENT_TYPE} from '../../lib/eventType';

ipcRenderer.once(EVENT_TYPE.PROXY_PROMPT.LOCALE_RENDER, (event: IpcMessageEvent, labels: string[]) => {
for (const label in labels) {
const labelElement = document.querySelector(`[data-string="${label}"]`);
if (labelElement) {
labelElement.innerHTML = labels[label];
}
}
});

ipcRenderer.once(EVENT_TYPE.PROXY_PROMPT.LOADED, () => {
const labels = [];
const dataStrings = document.querySelectorAll<HTMLDivElement>('[data-string]');

for (const index in dataStrings) {
const label = dataStrings[index];
if (label.dataset) {
labels.push(label.dataset.string);
}
}

ipcRenderer.send(EVENT_TYPE.PROXY_PROMPT.LOCALE_VALUES, labels);

const okButton = document.querySelector<HTMLButtonElement>('#okButton');
const cancelButton = document.querySelector<HTMLButtonElement>('#cancelButton');
const usernameInput = document.querySelector<HTMLInputElement>('#usernameInput');
const passwordInput = document.querySelector<HTMLInputElement>('#passwordInput');
const form = document.querySelector<HTMLInputElement>('#form');

if (cancelButton && okButton && usernameInput && passwordInput && form) {
usernameInput.focus();

const sendData = () => {
ipcRenderer.send(EVENT_TYPE.PROXY_PROMPT.SUBMITTED, {
password: passwordInput.value,
username: usernameInput.value,
});
window.close();
};

cancelButton.addEventListener('click', () => {
ipcRenderer.send(EVENT_TYPE.PROXY_PROMPT.CANCELED);
window.close();
});

form.addEventListener('submit', () => sendData());

window.addEventListener('keydown', event => {
if (event.key === 'Escape') {
ipcRenderer.send(EVENT_TYPE.PROXY_PROMPT.CANCELED);
window.close();
}
});
}
});
Loading

0 comments on commit 9cda28d

Please sign in to comment.