diff --git a/superset/assets/javascripts/explore/components/ControlHeader.jsx b/superset/assets/javascripts/explore/components/ControlHeader.jsx index bc474a68852f..ce00a9d76901 100644 --- a/superset/assets/javascripts/explore/components/ControlHeader.jsx +++ b/superset/assets/javascripts/explore/components/ControlHeader.jsx @@ -14,6 +14,7 @@ const propTypes = { onClick: PropTypes.func, hovered: PropTypes.bool, tooltipOnClick: PropTypes.func, + warning: PropTypes.string, }; const defaultProps = { @@ -75,6 +76,19 @@ export default class ControlHeader extends React.Component { {this.props.label} {' '} + {(this.props.warning) && + + {this.props.warning} + } + > + + + {' '} + + } {(this.props.validationErrors.length > 0) && ); } @@ -67,6 +70,7 @@ export default class TextAreaControl extends React.Component { placeholder={t('textarea')} onChange={this.onControlChange.bind(this)} value={this.props.value} + disabled={this.props.readOnly} style={{ height: this.props.height }} /> ); diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx index ea0feaa3785e..561ab65d0173 100644 --- a/superset/assets/javascripts/explore/stores/controls.jsx +++ b/superset/assets/javascripts/explore/stores/controls.jsx @@ -97,6 +97,11 @@ function jsFunctionControl(label, description, extraDescr = null, height = 100, {extraDescr} ), + mapStateToProps: state => ({ + warning: !state.common.conf.ENABLE_JAVASCRIPT_CONTROLS ? + t('This functionality is disabled in your environment for security reasons.') : null, + readOnly: !state.common.conf.ENABLE_JAVASCRIPT_CONTROLS, + }), }; } diff --git a/superset/config.py b/superset/config.py index 1ada471c6aaf..9e84c466b1da 100644 --- a/superset/config.py +++ b/superset/config.py @@ -370,6 +370,12 @@ class CeleryConfig(object): # Interval between consecutive polls when using Hive Engine HIVE_POLL_INTERVAL = 5 +# Allow for javascript controls components +# this enables programmers to customize certain charts (like the +# geospatial ones) by inputing javascript in controls. This exposes +# an XSS security vulnerability +ENABLE_JAVASCRIPT_CONTROLS = False + try: if CONFIG_PATH_ENV_VAR in os.environ: # Explicitly import config module that is not in pythonpath; useful diff --git a/superset/views/base.py b/superset/views/base.py index a909ed078a4b..7e0edc476d47 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -19,7 +19,10 @@ from superset.connectors.sqla.models import SqlaTable from superset.translations.utils import get_language_pack -FRONTEND_CONF_KEYS = ('SUPERSET_WEBSERVER_TIMEOUT',) +FRONTEND_CONF_KEYS = ( + 'SUPERSET_WEBSERVER_TIMEOUT', + 'ENABLE_JAVASCRIPT_CONTROLS', +) def get_error_msg(): diff --git a/superset/views/core.py b/superset/views/core.py index 46e16506823b..e5a6bf052f1d 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -73,6 +73,14 @@ else: DATASOURCE_ACCESS_ERR = __("You don't have access to this datasource") +FORM_DATA_KEY_BLACKLIST = [] +if not config.get('ENABLE_JAVASCRIPT_CONTROLS'): + FORM_DATA_KEY_BLACKLIST = [ + 'js_tooltip', + 'js_onclick_href', + 'js_data_mutator', + ] + def get_database_access_error_msg(database_name): return __('This view requires the database %(name)s or ' @@ -948,7 +956,10 @@ def get_form_data(self): if request.args.get('viz_type'): # Converting old URLs - d = cast_form_data(request.args) + d = cast_form_data(d) + + d = {k: v for k, v in d.items() if k not in FORM_DATA_KEY_BLACKLIST} + return d def get_viz(