From d6ad43cead6c92716ee765b97a0bdcedd2bb0b65 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 22 Jan 2020 17:32:23 -0500 Subject: [PATCH 1/8] feat: disable cookie access under restricted sandboxes When dash is embedded into an iframe with a sandbox attribute that only has allow-scripts, cookie access is disabled and dash fails to load. As such, we need to restrict our cookie usage by disabling functionality. This patch removes the disabled functionality in a graceful manner, allowing dash to load in very restricted iframes. --- CHANGELOG.md | 4 ++ dash-renderer/src/AccessDenied.react.js | 12 ++++-- dash-renderer/src/actions/index.js | 12 ++++-- tests/integration/renderer/test_iframe.py | 51 +++++++++++++++++++++++ 4 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 tests/integration/renderer/test_iframe.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b7e4a3c85..9938b93a52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] +### Fixed +- [#1080](https://github.com/plotly/dash/pull/1080) Handle case where dash fails to load when used inside an iframe with a sandbox attribute that only has allow-scripts + ## [1.8.0] - 2020-01-14 ### Added - [#1073](https://github.com/plotly/dash/pull/1073) Two new functions to simplify usage handling URLs and pathnames: `app.get_relative_path` & `app.trim_relative_path`. diff --git a/dash-renderer/src/AccessDenied.react.js b/dash-renderer/src/AccessDenied.react.js index 6dd7ab2808..0f987cb8c5 100644 --- a/dash-renderer/src/AccessDenied.react.js +++ b/dash-renderer/src/AccessDenied.react.js @@ -28,9 +28,15 @@ function AccessDenied(props) { { - document.cookie = - `${constants.OAUTH_COOKIE_NAME}=; ` + - 'expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + /* eslint no-empty: ["error", { "allowEmptyCatch": true }] */ + try { + document.cookie = + `${constants.OAUTH_COOKIE_NAME}=; ` + + 'expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + } catch (e) { + /* eslint-disable-next-line no-console */ + console.warn(e); + } window.location.reload(true); }} > diff --git a/dash-renderer/src/actions/index.js b/dash-renderer/src/actions/index.js index c6c9b6c772..05a47a68fd 100644 --- a/dash-renderer/src/actions/index.js +++ b/dash-renderer/src/actions/index.js @@ -55,9 +55,15 @@ export function hydrateInitialOutputs() { } export function getCSRFHeader() { - return { - 'X-CSRFToken': cookie.parse(document.cookie)._csrf_token, - }; + try { + return { + 'X-CSRFToken': cookie.parse(document.cookie)._csrf_token, + }; + } catch (e) { + /* eslint-disable-next-line no-console */ + console.warn(e); + return {}; + } } function triggerDefaultState(dispatch, getState) { diff --git a/tests/integration/renderer/test_iframe.py b/tests/integration/renderer/test_iframe.py new file mode 100644 index 0000000000..ac52f17164 --- /dev/null +++ b/tests/integration/renderer/test_iframe.py @@ -0,0 +1,51 @@ +from multiprocessing import Value + +import dash +from dash.dependencies import Input, Output +from dash.exceptions import PreventUpdate + +import dash_html_components as html + + +def test_rdif001_sandbox_allow_scripts(dash_duo): + app = dash.Dash(__name__) + call_count = Value("i") + + N_OUTPUTS = 50 + + app.layout = html.Div([ + html.Button("click me", id="btn"), + ] + [html.Div(id="output-{}".format(i)) for i in range(N_OUTPUTS)]) + + @app.callback( + [Output("output-{}".format(i), "children") for i in range(N_OUTPUTS)], + [Input("btn", "n_clicks")] + ) + def update_output(n_clicks): + if n_clicks is None: + raise PreventUpdate + + call_count.value += 1 + return ["{}={}".format(i, i + n_clicks) for i in range(N_OUTPUTS)] + + @app.server.after_request + def apply_cors(response): + response.headers["Access-Control-Allow-Origin"] = "*" + response.headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept, Authorization" + return response + + dash_duo.start_server(app) + + iframe = """ + + + + + """ + + html_content = iframe.format(dash_duo.server_url) + + dash_duo.driver.get("data:text/html;charset=utf-8," + html_content) + + assert not dash_duo.get_logs() From f936df11e277d0d481e1b75e5b556afdd1588e99 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 24 Jan 2020 16:54:40 -0500 Subject: [PATCH 2/8] tests: assert that the error length is 1 --- tests/integration/renderer/test_iframe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/renderer/test_iframe.py b/tests/integration/renderer/test_iframe.py index ac52f17164..8e3b822544 100644 --- a/tests/integration/renderer/test_iframe.py +++ b/tests/integration/renderer/test_iframe.py @@ -48,4 +48,4 @@ def apply_cors(response): dash_duo.driver.get("data:text/html;charset=utf-8," + html_content) - assert not dash_duo.get_logs() + assert len(dash_duo.get_logs()) == 1 From bab71de217a90d11abfdfc6ae956244afde33c18 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 24 Jan 2020 17:24:15 -0500 Subject: [PATCH 3/8] tests: 2 log lines are output in the restricted iframe test --- tests/integration/renderer/test_iframe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/renderer/test_iframe.py b/tests/integration/renderer/test_iframe.py index 8e3b822544..adbea55767 100644 --- a/tests/integration/renderer/test_iframe.py +++ b/tests/integration/renderer/test_iframe.py @@ -48,4 +48,4 @@ def apply_cors(response): dash_duo.driver.get("data:text/html;charset=utf-8," + html_content) - assert len(dash_duo.get_logs()) == 1 + assert len(dash_duo.get_logs()) == 2 From 0609adf6bf0249a37bfa920202655da4371c9bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 28 Jan 2020 11:52:11 -0500 Subject: [PATCH 4/8] add app interaction --- tests/integration/renderer/test_iframe.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/integration/renderer/test_iframe.py b/tests/integration/renderer/test_iframe.py index adbea55767..9c1c919c39 100644 --- a/tests/integration/renderer/test_iframe.py +++ b/tests/integration/renderer/test_iframe.py @@ -48,4 +48,10 @@ def apply_cors(response): dash_duo.driver.get("data:text/html;charset=utf-8," + html_content) + dash_duo.driver.switch_to.frame(0) + + dash_duo.wait_for_element('#output-0') + dash_duo.wait_for_element_by_id('btn').click() + dash_duo.wait_for_element('#output-0').text == '0=1' + assert len(dash_duo.get_logs()) == 2 From d28c961bfb1b21e723cae454f8d6b977eb49a210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 28 Jan 2020 12:12:25 -0500 Subject: [PATCH 5/8] there should be warnings --- tests/integration/renderer/test_iframe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/renderer/test_iframe.py b/tests/integration/renderer/test_iframe.py index 9c1c919c39..6a4a93f52c 100644 --- a/tests/integration/renderer/test_iframe.py +++ b/tests/integration/renderer/test_iframe.py @@ -54,4 +54,4 @@ def apply_cors(response): dash_duo.wait_for_element_by_id('btn').click() dash_duo.wait_for_element('#output-0').text == '0=1' - assert len(dash_duo.get_logs()) == 2 + assert len(dash_duo.get_logs()) != 0 From 74c9a9790ae1995c683b4658afe3f78cd9e4da16 Mon Sep 17 00:00:00 2001 From: Marc-Andre-Rivet Date: Tue, 28 Jan 2020 20:04:35 -0500 Subject: [PATCH 6/8] warn once --- dash-renderer/src/AccessDenied.react.js | 8 +++++--- dash-renderer/src/actions/index.js | 9 ++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/dash-renderer/src/AccessDenied.react.js b/dash-renderer/src/AccessDenied.react.js index 0f987cb8c5..f70625548a 100644 --- a/dash-renderer/src/AccessDenied.react.js +++ b/dash-renderer/src/AccessDenied.react.js @@ -1,10 +1,13 @@ /* global window:true, document:true */ import React from 'react'; -import {mergeRight} from 'ramda'; +import {mergeRight, once} from 'ramda'; import PropTypes from 'prop-types'; import * as styles from './styles/styles.js'; import * as constants from './constants/constants.js'; +/* eslint-disable-next-line no-console */ +const logWarningOnce = once(console.warn); + function AccessDenied(props) { const {config} = props; const fid = config.fid; @@ -34,8 +37,7 @@ function AccessDenied(props) { `${constants.OAUTH_COOKIE_NAME}=; ` + 'expires=Thu, 01 Jan 1970 00:00:01 GMT;'; } catch (e) { - /* eslint-disable-next-line no-console */ - console.warn(e); + logWarningOnce(e); } window.location.reload(true); }} diff --git a/dash-renderer/src/actions/index.js b/dash-renderer/src/actions/index.js index 05a47a68fd..653edb59f9 100644 --- a/dash-renderer/src/actions/index.js +++ b/dash-renderer/src/actions/index.js @@ -16,6 +16,7 @@ import { lensPath, mergeLeft, mergeDeepRight, + once, path, pluck, propEq, @@ -54,14 +55,16 @@ export function hydrateInitialOutputs() { }; } +/* eslint-disable-next-line no-console */ +const logWarningOnce = once(console.warn); + export function getCSRFHeader() { try { return { 'X-CSRFToken': cookie.parse(document.cookie)._csrf_token, }; } catch (e) { - /* eslint-disable-next-line no-console */ - console.warn(e); + logWarningOnce(e); return {}; } } @@ -77,7 +80,7 @@ function triggerDefaultState(dispatch, getState) { } catch (err) { dispatch( onError({ - type: 'backEnd', + logWarningOncetype: 'backEnd', error: { message: 'Circular Dependencies', html: err.toString(), From b87ad7bd0936c6f62aceab8656535787d8d3f1c1 Mon Sep 17 00:00:00 2001 From: Marc-Andre-Rivet Date: Tue, 28 Jan 2020 20:18:21 -0500 Subject: [PATCH 7/8] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9938b93a52..8e055f7c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - [#1080](https://github.com/plotly/dash/pull/1080) Handle case where dash fails to load when used inside an iframe with a sandbox attribute that only has allow-scripts + ## [1.8.0] - 2020-01-14 ### Added - [#1073](https://github.com/plotly/dash/pull/1073) Two new functions to simplify usage handling URLs and pathnames: `app.get_relative_path` & `app.trim_relative_path`. From 126b7a160513d9c12e7bf36c31c3d482cbe83a40 Mon Sep 17 00:00:00 2001 From: Marc-Andre-Rivet Date: Tue, 28 Jan 2020 20:55:31 -0500 Subject: [PATCH 8/8] undo bad copypasta --- dash-renderer/src/actions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash-renderer/src/actions/index.js b/dash-renderer/src/actions/index.js index 653edb59f9..fc582775a7 100644 --- a/dash-renderer/src/actions/index.js +++ b/dash-renderer/src/actions/index.js @@ -80,7 +80,7 @@ function triggerDefaultState(dispatch, getState) { } catch (err) { dispatch( onError({ - logWarningOncetype: 'backEnd', + type: 'backEnd', error: { message: 'Circular Dependencies', html: err.toString(),