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
 ):