From 544d2ed3bbaeb9791cb89c55c2220d423d5782a0 Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Mon, 31 Jul 2017 18:01:26 -0700 Subject: [PATCH 1/2] Prevent XSS in Markup viz We protect the browser by sandboxing the user code inside an iframe --- superset/__init__.py | 27 ++++++++++++++----- .../components/controls/TextAreaControl.jsx | 2 +- superset/assets/stylesheets/superset.less | 6 ++++- superset/assets/visualizations/markup.js | 22 +++++++++++++-- superset/viz.py | 4 +-- 5 files changed, 48 insertions(+), 13 deletions(-) diff --git a/superset/__init__.py b/superset/__init__.py index 9576458804337..2e44ebd590180 100644 --- a/superset/__init__.py +++ b/superset/__init__.py @@ -32,22 +32,35 @@ app.config.from_object(CONFIG_MODULE) conf = app.config +################################################################# # Handling manifest file logic at app start +################################################################# MANIFEST_FILE = APP_DIR + '/static/assets/dist/manifest.json' -get_manifest_file = lambda x: x manifest = {} -try: - with open(MANIFEST_FILE, 'r') as f: - manifest = json.load(f) - get_manifest_file = lambda x: '/static/assets/dist/' + manifest.get(x, '') -except Exception: - print("no manifest file found at " + MANIFEST_FILE) +def parse_manifest_json(): + global manifest + try: + with open(MANIFEST_FILE, 'r') as f: + manifest = json.load(f) + except Exception: + print("no manifest file found at " + MANIFEST_FILE) + + +def get_manifest_file(filename): + if app.debug: + parse_manifest_json() + return '/static/assets/dist/' + manifest.get(filename, '') + +parse_manifest_json() + @app.context_processor def get_js_manifest(): return dict(js_manifest=get_manifest_file) +################################################################# + for bp in conf.get('BLUEPRINTS'): try: diff --git a/superset/assets/javascripts/explore/components/controls/TextAreaControl.jsx b/superset/assets/javascripts/explore/components/controls/TextAreaControl.jsx index 4fe9b7716d891..31fe2bb25ddbf 100644 --- a/superset/assets/javascripts/explore/components/controls/TextAreaControl.jsx +++ b/superset/assets/javascripts/explore/components/controls/TextAreaControl.jsx @@ -76,7 +76,7 @@ export default class TextAreaControl extends React.Component { modalTitle={controlHeader} triggerNode={ } modalBody={this.renderEditor(true)} diff --git a/superset/assets/stylesheets/superset.less b/superset/assets/stylesheets/superset.less index aa5678cea4080..f516e6e97f487 100644 --- a/superset/assets/stylesheets/superset.less +++ b/superset/assets/stylesheets/superset.less @@ -255,4 +255,8 @@ div.widget .slice_container { .panel .table-responsive{ width: 98%; } -} \ No newline at end of file +} +iframe { + border: none; + width: 100%; +} diff --git a/superset/assets/visualizations/markup.js b/superset/assets/visualizations/markup.js index ba0ad446d001d..379e2ef3dc389 100644 --- a/superset/assets/visualizations/markup.js +++ b/superset/assets/visualizations/markup.js @@ -4,11 +4,29 @@ require('./markup.css'); function markupWidget(slice, payload) { $('#code').attr('rows', '15'); - slice.container.css({ + const jqdiv = slice.container; + jqdiv.css({ overflow: 'auto', height: slice.container.height(), }); - slice.container.html(payload.data.html); + + const iframeId = `if__${slice.containerId}`; + const html = ` + + + + + + ${payload.data.html} + + `; + jqdiv.html(` + `); + $('#' + iframeId)[0].srcdoc = html; } module.exports = markupWidget; diff --git a/superset/viz.py b/superset/viz.py index c2d65e6c8524e..100d8bbd6fce2 100644 --- a/superset/viz.py +++ b/superset/viz.py @@ -28,7 +28,7 @@ from six import string_types, PY3 from dateutil import relativedelta as rdelta -from superset import app, utils, cache +from superset import app, utils, cache, get_manifest_file from superset.utils import DTTM_ALIAS config = app.config @@ -439,7 +439,7 @@ def get_data(self, df): code = self.form_data.get("code", '') if markup_type == "markdown": code = markdown(code) - return dict(html=code) + return dict(html=code, theme_css=get_manifest_file('theme.css')) class SeparatorViz(MarkupViz): From 1b3d135d4f7407f843212325b3a7a66ab1faf19f Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Thu, 10 Aug 2017 21:11:53 -0700 Subject: [PATCH 2/2] Helvetica --- superset/assets/stylesheets/less/cosmo/variables.less | 2 +- superset/assets/stylesheets/superset.less | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/superset/assets/stylesheets/less/cosmo/variables.less b/superset/assets/stylesheets/less/cosmo/variables.less index ce6b85a3524b8..fb2abf696d45c 100644 --- a/superset/assets/stylesheets/less/cosmo/variables.less +++ b/superset/assets/stylesheets/less/cosmo/variables.less @@ -41,7 +41,7 @@ // //## Font, line-height, and color for body text, headings, and more. -@font-family-sans-serif: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; +@font-family-sans-serif: Helvetica, Arial; @font-family-serif: Georgia, "Times New Roman", Times, serif; //** Default monospace fonts for ``, ``, and `
`.
diff --git a/superset/assets/stylesheets/superset.less b/superset/assets/stylesheets/superset.less
index f516e6e97f487..bacc18adc0efa 100644
--- a/superset/assets/stylesheets/superset.less
+++ b/superset/assets/stylesheets/superset.less
@@ -1,9 +1,3 @@
-@font-face {
-  font-family: "Roboto";
-  src: url("./fonts/Roboto-Regular.woff2") format("woff2"),
-       url("./fonts/Roboto-Regular.woff") format("woff");
-}
-
 body {
     margin: 0px !important;
 }