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