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 2026/08/28 22:03:16 UTC

(superset) branch master updated: fix(explore): catch TemplateError when validating access for query-backed form_data (#43470)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 30402b412c2 fix(explore): catch TemplateError when validating access for query-backed form_data (#43470)
30402b412c2 is described below

commit 30402b412c2c4f8a30b65419944d519143b3272b
Author: Elizabeth Thompson <es...@gmail.com>
AuthorDate: Fri Aug 28 15:03:08 2026 -0700

    fix(explore): catch TemplateError when validating access for query-backed form_data (#43470)
---
 superset/explore/form_data/api.py      |  9 +++++++++
 superset/explore/utils.py              | 11 ++++++++++-
 tests/unit_tests/explore/utils_test.py | 25 ++++++++++++++++++++++++-
 3 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/superset/explore/form_data/api.py b/superset/explore/form_data/api.py
index 6c882d92a6f..d8c1c3bfd32 100644
--- a/superset/explore/form_data/api.py
+++ b/superset/explore/form_data/api.py
@@ -30,6 +30,7 @@ from superset.commands.temporary_cache.exceptions import (
     TemporaryCacheResourceNotFoundError,
 )
 from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP
+from superset.exceptions import SupersetTemplateException
 from superset.explore.form_data.schemas import FormDataPostSchema, FormDataPutSchema
 from superset.extensions import event_logger
 from superset.views.base_api import BaseSupersetApi, requires_json, statsd_metrics
@@ -110,6 +111,8 @@ class ExploreFormDataRestApi(BaseSupersetApi):
             return self.response(403, message=str(ex))
         except TemporaryCacheResourceNotFoundError as ex:
             return self.response(404, message=str(ex))
+        except SupersetTemplateException as ex:
+            return self.response(ex.status, message=str(ex))
 
     @expose("/form_data/<string:key>", methods=("PUT",))
     @protect()
@@ -183,6 +186,8 @@ class ExploreFormDataRestApi(BaseSupersetApi):
             return self.response(403, message=str(ex))
         except TemporaryCacheResourceNotFoundError as ex:
             return self.response(404, message=str(ex))
+        except SupersetTemplateException as ex:
+            return self.response(ex.status, message=str(ex))
 
     @expose("/form_data/<string:key>", methods=("GET",))
     @protect()
@@ -234,6 +239,8 @@ class ExploreFormDataRestApi(BaseSupersetApi):
             return self.response(403, message=str(ex))
         except TemporaryCacheResourceNotFoundError as ex:
             return self.response(404, message=str(ex))
+        except SupersetTemplateException as ex:
+            return self.response(ex.status, message=str(ex))
 
     @expose("/form_data/<string:key>", methods=("DELETE",))
     @protect()
@@ -286,3 +293,5 @@ class ExploreFormDataRestApi(BaseSupersetApi):
             return self.response(403, message=str(ex))
         except TemporaryCacheResourceNotFoundError as ex:
             return self.response(404, message=str(ex))
+        except SupersetTemplateException as ex:
+            return self.response(ex.status, message=str(ex))
diff --git a/superset/explore/utils.py b/superset/explore/utils.py
index 28e4db2b916..0535bbc01fb 100644
--- a/superset/explore/utils.py
+++ b/superset/explore/utils.py
@@ -16,6 +16,8 @@
 # under the License.
 from typing import Optional
 
+from jinja2.exceptions import TemplateError
+
 from superset import security_manager
 from superset.commands.chart.exceptions import (
     ChartAccessDeniedError,
@@ -33,6 +35,7 @@ from superset.commands.exceptions import (
 from superset.daos.chart import ChartDAO
 from superset.daos.dataset import DatasetDAO
 from superset.daos.query import QueryDAO
+from superset.exceptions import SupersetTemplateException
 from superset.utils.core import DatasourceType
 
 
@@ -53,7 +56,13 @@ def check_query_access(query_id: int) -> Optional[bool]:
         # Access checks below, no need to validate them twice as they can be expensive.
         query = QueryDAO.find_by_id(query_id, skip_base_filter=True)
         if query:
-            security_manager.raise_for_access(query=query)
+            try:
+                security_manager.raise_for_access(query=query)
+            except TemplateError as ex:
+                # raise_for_access() Jinja-renders the query's SQL to resolve
+                # the tables it touches; a malformed template surfaces here as
+                # a raw jinja2 exception rather than a Superset one.
+                raise SupersetTemplateException(str(ex)) from ex
             return True
     raise QueryNotFoundValidationError()
 
diff --git a/tests/unit_tests/explore/utils_test.py b/tests/unit_tests/explore/utils_test.py
index 54555d2814e..c7778a7f0dc 100644
--- a/tests/unit_tests/explore/utils_test.py
+++ b/tests/unit_tests/explore/utils_test.py
@@ -15,6 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 from flask_appbuilder.security.sqla.models import User
+from jinja2.exceptions import TemplateSyntaxError
 from pytest import raises  # noqa: PT013
 from pytest_mock import MockerFixture
 
@@ -30,7 +31,7 @@ from superset.commands.exceptions import (
     DatasourceNotFoundValidationError,
     QueryNotFoundValidationError,
 )
-from superset.exceptions import SupersetSecurityException
+from superset.exceptions import SupersetSecurityException, SupersetTemplateException
 from superset.utils.core import DatasourceType, override_user
 
 dataset_find_by_id = "superset.daos.dataset.DatasetDAO.find_by_id"
@@ -340,6 +341,28 @@ def test_query_has_access(mocker: MockerFixture) -> None:
     )
 
 
+def test_query_malformed_jinja_template(mocker: MockerFixture) -> None:
+    """
+    ``raise_for_access(query=...)`` Jinja-renders the query's SQL to resolve
+    the tables it touches. A malformed template must surface as a
+    ``SupersetTemplateException``, not the raw ``jinja2`` exception.
+    """
+    from superset.explore.utils import check_datasource_access
+    from superset.models.sql_lab import Query
+
+    mocker.patch(query_find_by_id, return_value=Query())
+    mocker.patch(
+        raise_for_access,
+        side_effect=TemplateSyntaxError("unexpected end of template", lineno=1),
+    )
+
+    with raises(SupersetTemplateException):  # noqa: PT012
+        check_datasource_access(
+            datasource_id=1,
+            datasource_type=DatasourceType.QUERY,
+        )
+
+
 def test_query_no_access(mocker: MockerFixture, client) -> None:
     from superset.connectors.sqla.models import SqlaTable
     from superset.explore.utils import check_datasource_access