From 900a8bd56ceda2bcd8a847c3f3135de179f94c1d Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 8 Jan 2021 15:39:27 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20enable=20dynamic=20csp?= =?UTF-8?q?=20rewriting=20when=20origin=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/content-security-policy.js | 20 ++++++++-- ui/desktop/electron-app/src/handlers.js | 16 +++----- ui/desktop/electron-app/src/index.js | 5 +++ .../electron-app/src/runtime-settings.js | 37 +++++++++++++++++++ 4 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 ui/desktop/electron-app/src/runtime-settings.js diff --git a/ui/desktop/electron-app/src/content-security-policy.js b/ui/desktop/electron-app/src/content-security-policy.js index dffc1c524..12c25834d 100644 --- a/ui/desktop/electron-app/src/content-security-policy.js +++ b/ui/desktop/electron-app/src/content-security-policy.js @@ -1,3 +1,4 @@ +const runtimeSettings = require('./runtime-settings.js'); const isDev = require('electron-is-dev'); const csp = { @@ -23,10 +24,21 @@ const enableDevCSP = () => { csp['connect-src'].push("ws://localhost:7020"); }; -const generateCSPHeader = () => Object - .keys(csp) - .map(key => `${key} ${csp[key].join(' ')};`) - .join(' '); +const generateCSPHeader = () => { + const policy = Object.assign({}, csp); + + // If an origin is specified, add it to the connect-src directive + if (runtimeSettings.origin) { + policy['connect-src'] = policy['connect-src'].slice(); + policy['connect-src'].push(runtimeSettings.origin); + console.log(`[content-security-policy] Including user-specified origin "${runtimeSettings.origin}" to connect-src`); + } + + return Object + .keys(policy) + .map(key => `${key} ${policy[key].join(' ')};`) + .join(' '); +} if (isDev) enableDevCSP(); diff --git a/ui/desktop/electron-app/src/handlers.js b/ui/desktop/electron-app/src/handlers.js index 4c3735fc3..fa3aaf8b1 100644 --- a/ui/desktop/electron-app/src/handlers.js +++ b/ui/desktop/electron-app/src/handlers.js @@ -1,24 +1,18 @@ const handle = require('./ipc-handler'); const boundaryCli = require('./boundary-cli'); - - -let origin = ''; +const runtimeSettings = require('./runtime-settings.js'); /** * Returns the current runtime origin, which is used by the main thread to * rewrite the CSP to allow requests. */ -handle('getOrigin', () => origin); +handle('getOrigin', () => runtimeSettings.origin); /** - * TODO: sets the main process origin to the specified value. This value will - * be used to rewrite the CSP to allow requests to that origin. If the origin - * was not previously set, the main process will set it and restart - * the renderer. - * - * For now, we update the internal origin but do not yet rewrite the CSP. + * Sets the origin to be used in the content security policy and triggers + * a main window reload. */ -handle('setOrigin', (requestOrigin) => origin = requestOrigin); +handle('setOrigin', (requestOrigin) => runtimeSettings.origin = requestOrigin); /** * Check for boundary cli existence diff --git a/ui/desktop/electron-app/src/index.js b/ui/desktop/electron-app/src/index.js index 9f2f19759..6b1b5a548 100644 --- a/ui/desktop/electron-app/src/index.js +++ b/ui/desktop/electron-app/src/index.js @@ -7,6 +7,7 @@ const { const { session, app, protocol, BrowserWindow, ipcMain } = require('electron'); require('./handlers.js'); +const runtimeSettings = require('./runtime-settings.js'); const { generateCSPHeader } = require('./content-security-policy.js'); const isDev = require('electron-is-dev'); @@ -100,6 +101,10 @@ app.on('ready', async () => { }, }); + // If the user-specified origin changes, reload the page so that + // the CSP can be refreshed with the this source allowed + runtimeSettings.onOriginChange(() => mainWindow.loadURL(emberAppURL)); + // If you want to open up dev tools programmatically, call // mainWindow.openDevTools(); diff --git a/ui/desktop/electron-app/src/runtime-settings.js b/ui/desktop/electron-app/src/runtime-settings.js new file mode 100644 index 000000000..def591318 --- /dev/null +++ b/ui/desktop/electron-app/src/runtime-settings.js @@ -0,0 +1,37 @@ +// Provides a singleton class instance to enable a consistent view of +// runtime settings across the application. + +// Runtime settings are any configuration, especially user-specified, +// set up at runtime. For example, the Boundary origin is a runtime setting. + +class RuntimeSettings { + + // Internal private origin is exposed via getter/setter below. + #origin = undefined; + + /** + * The user-specified Boundary origin, which should be allowed by CSP. + * @type {?string} + */ + get origin() { return this.#origin; } + set origin(origin) { + this.#origin = origin; + this.triggerOriginChanged(); + } + + // Quick and dirty event handler pattern to enable the application to respond + // when the origin is changed. + #originWatchers = []; + + onOriginChange(fn) { + this.#originWatchers.push(fn); + } + + triggerOriginChanged() { + this.#originWatchers.forEach(fn => fn(this.#origin)); + } +} + +const runtimeSettings = new RuntimeSettings(); + +module.exports = runtimeSettings;