From 06d547fbace346f79dc3d8beb1fa2bfcd9125890 Mon Sep 17 00:00:00 2001 From: Grace Guo Date: Mon, 29 Jul 2019 16:22:47 -0700 Subject: [PATCH] [feature flag] Enforce csrf protection on explore_json endpoint (#7935) also added a section for featured flags in http://superset.incubator.apache.org/installation.html --- docs/installation.rst | 24 +++++++++++++++++++++++- superset/config.py | 3 ++- superset/views/base.py | 8 ++++++++ superset/views/core.py | 11 +++++++++-- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index d82f0f718..68bc5ce06 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -667,7 +667,7 @@ DOMAIN SHARDING Chrome allows up to 6 open connections per domain at a time. When there are more than 6 slices in dashboard, a lot of time fetch requests are queued up and wait for -next available socket. PR (`#5039 `) adds domain sharding to Superset, +next available socket. `PR 5039 `_ adds domain sharding to Superset, and this feature will be enabled by configuration only (by default Superset doesn't allow cross-domain request). @@ -1161,3 +1161,25 @@ Then we can add this two lines to ``superset_config.py``: from custom_sso_security_manager import CustomSsoSecurityManager CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager + +Feature Flags +--------------------------- + +Because of a wide variety of users, Superset has some features that are not enabled by default. For example, some users have stronger security restrictions, while some others may not. So Superset allow users to enable or disable some features by config. For feature owners, you can add optional functionalities in Superset, but will be only affected by a subset of users. + +You can enable or disable features with flag from ``superset_config.py``: + +.. code-block:: python + + DEFAULT_FEATURE_FLAGS = { + 'CLIENT_CACHE': False, + 'ENABLE_EXPLORE_JSON_CSRF_PROTECTION': False + } + +Here is a list of flags and descriptions: + +* ENABLE_EXPLORE_JSON_CSRF_PROTECTION + + * For some security concerns, you may need to enforce CSRF protection on all query request to explore_json endpoint. In Superset, we use `flask-csrf `_ add csrf protection for all POST requests, but this protection doesn't apply to GET method. + + * When ENABLE_EXPLORE_JSON_CSRF_PROTECTION is set to true, your users cannot make GET request to explore_json. The default value for this feature False (current behavior), explore_json accepts both GET and POST request. See `PR 7935 `_ for more details. diff --git a/superset/config.py b/superset/config.py index b676e5179..01bc16a58 100644 --- a/superset/config.py +++ b/superset/config.py @@ -204,7 +204,8 @@ LANGUAGES = { # will result in combined feature flags of { 'FOO': True, 'BAR': True, 'BAZ': True } DEFAULT_FEATURE_FLAGS = { # Experimental feature introducing a client (browser) cache - "CLIENT_CACHE": False + "CLIENT_CACHE": False, + "ENABLE_EXPLORE_JSON_CSRF_PROTECTION": False, } # A function that receives a dict of all feature flags diff --git a/superset/views/base.py b/superset/views/base.py index c82db76a7..37ba99920 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -32,6 +32,7 @@ from flask_babel import gettext as __ from flask_babel import lazy_gettext as _ from flask_wtf.form import FlaskForm import simplejson as json +from werkzeug.exceptions import HTTPException from wtforms.fields.core import Field, UnboundField import yaml @@ -134,6 +135,13 @@ def handle_api_exception(f): stacktrace=utils.get_stacktrace(), status=e.status, ) + except HTTPException as e: + logging.exception(e) + return json_error_response( + utils.error_msg_from_exception(e), + stacktrace=traceback.format_exc(), + status=e.code, + ) except Exception as e: logging.exception(e) return json_error_response( diff --git a/superset/views/core.py b/superset/views/core.py index 558a30ce0..eaf789154 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -53,6 +53,7 @@ from superset import ( db, event_logger, get_feature_flags, + is_feature_enabled, results_backend, security_manager, sql_lab, @@ -1047,12 +1048,18 @@ class Superset(BaseSupersetView): payload = viz_obj.get_payload() return data_payload_response(*viz_obj.payload_json_and_has_error(payload)) + EXPLORE_JSON_METHODS = ["POST"] + if not is_feature_enabled("ENABLE_EXPLORE_JSON_CSRF_PROTECTION"): + EXPLORE_JSON_METHODS.append("GET") + @event_logger.log_this @api @has_access_api @handle_api_exception - @expose("/explore_json///", methods=["GET", "POST"]) - @expose("/explore_json/", methods=["GET", "POST"]) + @expose( + "/explore_json///", methods=EXPLORE_JSON_METHODS + ) + @expose("/explore_json/", methods=EXPLORE_JSON_METHODS) @etag_cache(CACHE_DEFAULT_TIMEOUT, check_perms=check_datasource_perms) def explore_json(self, datasource_type=None, datasource_id=None): """Serves all request that GET or POST form_data