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/27 22:06:59 UTC
(superset) branch master updated: fix(tags): catch TemplateError when validating access for tagged SQL Lab queries (#43423)
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 7b145a520c9 fix(tags): catch TemplateError when validating access for tagged SQL Lab queries (#43423)
7b145a520c9 is described below
commit 7b145a520c921098b5b70ef6e3963c5208fb88a7
Author: Elizabeth Thompson <es...@gmail.com>
AuthorDate: Thu Aug 27 15:06:46 2026 -0700
fix(tags): catch TemplateError when validating access for tagged SQL Lab queries (#43423)
Co-authored-by: Claude Opus 4.8 <no...@anthropic.com>
---
superset/commands/tag/create.py | 28 ++++++++++-
tests/unit_tests/tags/commands/create_test.py | 69 +++++++++++++++++++++++++++
2 files changed, 95 insertions(+), 2 deletions(-)
diff --git a/superset/commands/tag/create.py b/superset/commands/tag/create.py
index 6cdfb53ac61..0c45d154e4e 100644
--- a/superset/commands/tag/create.py
+++ b/superset/commands/tag/create.py
@@ -18,13 +18,15 @@ import logging
from functools import partial
from typing import Any
+from jinja2.exceptions import TemplateError
+
from superset import security_manager
from superset.commands.base import BaseCommand, CreateMixin
from superset.commands.tag.exceptions import TagCreateFailedError, TagInvalidError
from superset.commands.tag.utils import to_object_model, to_object_type
from superset.commands.utils import current_user_can_modify_object
from superset.daos.tag import TagDAO
-from superset.exceptions import SupersetSecurityException
+from superset.exceptions import SupersetParseError, SupersetSecurityException
from superset.tags.models import ObjectType, TagType
from superset.utils.decorators import on_error, transaction
@@ -95,8 +97,30 @@ class CreateCustomTagCommand(CreateMixin, BaseCommand):
)
)
except SupersetSecurityException:
+ # A routine, expected authorization denial; swallowed silently by
+ # design (no logging) and surfaced to the caller as a validation
+ # failure rather than an unhandled 500.
+ exceptions.append(
+ TagCreateFailedError(
+ f"Could not validate access for {object_type} {object_id}"
+ )
+ )
+ except (TemplateError, SupersetParseError) as ex:
+ # Authorizing a saved query parses its Jinja-templated SQL to resolve
+ # table references. Malformed Jinja (TemplateError) or an
+ # unresolvable partition macro (SupersetParseError) is a validation
+ # failure, not an unhandled 500 -- but unlike an access denial it is
+ # genuinely unexpected, so log it for server-side visibility and
+ # preserve the underlying error text instead of discarding it.
+ logger.warning(
+ "Could not parse query %s while validating tag access: %s",
+ object_id,
+ str(ex),
+ )
exceptions.append(
- TagCreateFailedError(f"Access denied for {object_type} {object_id}")
+ TagCreateFailedError(
+ f"Could not validate access for {object_type} {object_id}: {ex}"
+ )
)
diff --git a/tests/unit_tests/tags/commands/create_test.py b/tests/unit_tests/tags/commands/create_test.py
index 58d3325b880..292e343f3fb 100644
--- a/tests/unit_tests/tags/commands/create_test.py
+++ b/tests/unit_tests/tags/commands/create_test.py
@@ -108,6 +108,75 @@ def test_create_command_success(session_with_data: Session, mocker: MockerFixtur
)
+def test_validate_object_access_query_malformed_jinja(
+ session_with_data: Session, mocker: MockerFixture
+):
+ """A saved query whose Jinja-templated SQL fails to parse during access
+ checks must surface as a validation error, not an unhandled
+ ``jinja2.TemplateError`` escaping as a 500.
+
+ When ``raise_for_access(query=...)`` authorizes a saved query via
+ per-table permissions it parses the query's Jinja SQL (e.g. an unclosed
+ ``{% if %}`` block raises ``TemplateSyntaxError``). Mock that call to raise
+ the ``TemplateError`` directly so the test stays hermetic and does not open
+ a live DB connection to introspect table-level perms.
+ """
+ from jinja2.exceptions import TemplateError
+
+ from superset.commands.tag.create import CreateCustomTagCommand
+ from superset.commands.tag.exceptions import TagInvalidError
+ from superset.models.sql_lab import SavedQuery
+ from superset.tags.models import ObjectType
+
+ query = db.session.query(SavedQuery).first()
+
+ mocker.patch("superset.commands.tag.create.to_object_model", return_value=query)
+ mocker.patch(
+ "superset.commands.tag.create.security_manager.raise_for_access",
+ side_effect=TemplateError("unclosed {% if %}"),
+ )
+
+ command = CreateCustomTagCommand(ObjectType.query, query.id, ["tag"])
+
+ with pytest.raises(TagInvalidError):
+ command.validate()
+
+
+def test_validate_object_access_query_unresolvable_partition_macro(
+ session_with_data: Session, mocker: MockerFixture
+):
+ """A saved query whose partition macro cannot be resolved statically raises
+ ``SupersetParseError`` during access checks. Like ``TemplateError``, it is a
+ sibling of ``SupersetSecurityException`` under ``SupersetErrorException`` and
+ would otherwise escape as an unhandled 500, so it must also surface as a
+ validation error.
+
+ Mock ``raise_for_access`` to raise the error directly so the test stays
+ hermetic and does not open a live DB connection to introspect table perms.
+ """
+ from superset.commands.tag.create import CreateCustomTagCommand
+ from superset.commands.tag.exceptions import TagInvalidError
+ from superset.exceptions import SupersetParseError
+ from superset.models.sql_lab import SavedQuery
+ from superset.tags.models import ObjectType
+
+ query = db.session.query(SavedQuery).first()
+
+ mocker.patch("superset.commands.tag.create.to_object_model", return_value=query)
+ mocker.patch(
+ "superset.commands.tag.create.security_manager.raise_for_access",
+ side_effect=SupersetParseError(
+ sql="select * from {{ latest_partition('foo') }}",
+ message="Unresolvable partition macro",
+ ),
+ )
+
+ command = CreateCustomTagCommand(ObjectType.query, query.id, ["tag"])
+
+ with pytest.raises(TagInvalidError):
+ command.validate()
+
+
def test_create_command_success_clear(
session_with_data: Session, mocker: MockerFixture
):