You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by el...@apache.org on 2023/12/02 01:20:59 UTC

(superset) 04/08: chore: rate limit requests (#24324)

This is an automated email from the ASF dual-hosted git repository.

elizabeth pushed a commit to branch 2.1
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 3e792736144bd089c6b05f1afb42d87bce0c267a
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Fri Aug 11 09:35:31 2023 -0700

    chore: rate limit requests (#24324)
---
 superset/config.py                              | 14 ++++++++++++++
 superset/dashboards/api.py                      |  2 +-
 superset/models/dashboard.py                    |  3 ++-
 superset/utils/dashboard_import_export.py       |  4 ++--
 superset/views/dashboard/views.py               |  2 +-
 tests/integration_tests/superset_test_config.py |  2 ++
 6 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/superset/config.py b/superset/config.py
index f2d9fa5adf..39ce66e875 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -278,6 +278,20 @@ PROXY_FIX_CONFIG = {"x_for": 1, "x_proto": 1, "x_host": 1, "x_port": 1, "x_prefi
 # Configuration for scheduling queries from SQL Lab.
 SCHEDULED_QUERIES: Dict[str, Any] = {}
 
+# FAB Rate limiting: this is a security feature for preventing DDOS attacks. The
+# feature is on by default to make Superset secure by default, but you should
+# fine tune the limits to your needs. You can read more about the different
+# parameters here: https://flask-limiter.readthedocs.io/en/stable/configuration.html
+RATELIMIT_ENABLED = True
+RATELIMIT_APPLICATION = "50 per second"
+AUTH_RATE_LIMITED = True
+AUTH_RATE_LIMIT = "5 per second"
+# A storage location conforming to the scheme in storage-scheme. See the limits
+# library for allowed values: https://limits.readthedocs.io/en/stable/storage.html
+# RATELIMIT_STORAGE_URI = "redis://host:port"
+# A callable that returns the unique identity of the current request.
+# RATELIMIT_REQUEST_IDENTIFIER = flask.Request.endpoint
+
 # ------------------------------
 # GLOBALS FOR APP Builder
 # ------------------------------
diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py
index 1a476a0a97..6f478e22cc 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -813,7 +813,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
             Dashboard.id.in_(requested_ids)
         )
         query = self._base_filters.apply_all(query)
-        ids = [item.id for item in query.all()]
+        ids = {item.id for item in query.all()}
         if not ids:
             return self.response_404()
         export = Dashboard.export_dashboards(ids)
diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py
index 60a8ea0e30..e2192ae2f2 100644
--- a/superset/models/dashboard.py
+++ b/superset/models/dashboard.py
@@ -372,7 +372,8 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin):
 
     @classmethod
     def export_dashboards(  # pylint: disable=too-many-locals
-        cls, dashboard_ids: List[int]
+        cls,
+        dashboard_ids: Set[int],
     ) -> str:
         copied_dashboards = []
         datasource_ids = set()
diff --git a/superset/utils/dashboard_import_export.py b/superset/utils/dashboard_import_export.py
index fc61d0a422..eef8cbe6df 100644
--- a/superset/utils/dashboard_import_export.py
+++ b/superset/utils/dashboard_import_export.py
@@ -27,8 +27,8 @@ def export_dashboards(session: Session) -> str:
     """Returns all dashboards metadata as a json dump"""
     logger.info("Starting export")
     dashboards = session.query(Dashboard)
-    dashboard_ids = []
+    dashboard_ids = set()
     for dashboard in dashboards:
-        dashboard_ids.append(dashboard.id)
+        dashboard_ids.add(dashboard.id)
     data = Dashboard.export_dashboards(dashboard_ids)
     return data
diff --git a/superset/views/dashboard/views.py b/superset/views/dashboard/views.py
index 52cb2da82e..e476a88f31 100644
--- a/superset/views/dashboard/views.py
+++ b/superset/views/dashboard/views.py
@@ -76,7 +76,7 @@ class DashboardModelView(
     @expose("/export_dashboards_form")
     def download_dashboards(self) -> FlaskResponse:
         if request.args.get("action") == "go":
-            ids = request.args.getlist("id")
+            ids = set(request.args.getlist("id"))
             return Response(
                 DashboardModel.export_dashboards(ids),
                 headers=generate_download_headers("json"),
diff --git a/tests/integration_tests/superset_test_config.py b/tests/integration_tests/superset_test_config.py
index 19c2cc000f..76b83fb465 100644
--- a/tests/integration_tests/superset_test_config.py
+++ b/tests/integration_tests/superset_test_config.py
@@ -96,6 +96,8 @@ REDIS_CELERY_DB = os.environ.get("REDIS_CELERY_DB", 2)
 REDIS_RESULTS_DB = os.environ.get("REDIS_RESULTS_DB", 3)
 REDIS_CACHE_DB = os.environ.get("REDIS_CACHE_DB", 4)
 
+RATELIMIT_ENABLED = False
+
 
 CACHE_CONFIG = {
     "CACHE_TYPE": "RedisCache",