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

aminghadersohi 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 8a7be3b327b fix: avoid provider calls when rendering datasource access 
denials (#44432)
8a7be3b327b is described below

commit 8a7be3b327b2cc6f82935e11dea076766c200c40
Author: Mike Bridge <[email protected]>
AuthorDate: Wed Sep 30 13:51:27 2026 -0600

    fix: avoid provider calls when rendering datasource access denials (#44432)
---
 .../charts/semantic_save_tests.py                  | 82 +++++++++++++++++++++-
 .../security/test_permission_instructions_link.py  |  4 +-
 2 files changed, 83 insertions(+), 3 deletions(-)

diff --git a/tests/integration_tests/charts/semantic_save_tests.py 
b/tests/integration_tests/charts/semantic_save_tests.py
index 06d8e86d5d1..af900f7ff6e 100644
--- a/tests/integration_tests/charts/semantic_save_tests.py
+++ b/tests/integration_tests/charts/semantic_save_tests.py
@@ -17,7 +17,7 @@
 """HTTP chart saves must preserve semantic identity and datasource access 
checks."""
 
 from typing import Any
-from unittest.mock import Mock, patch
+from unittest.mock import Mock, patch, PropertyMock
 
 from flask import Response
 from parameterized import parameterized
@@ -27,6 +27,7 @@ from superset.exceptions import SupersetSecurityException
 from superset.extensions import db, security_manager
 from superset.models.slice import Slice
 from superset.semantic_layers.models import SemanticLayer, SemanticView
+from superset.subjects.models import Subject
 from superset.utils import json
 from tests.integration_tests.base_tests import SupersetTestCase
 
@@ -121,6 +122,85 @@ class TestSemanticChartSave(SupersetTestCase):
             db.session.delete(layer)
             db.session.commit()
 
+    @parameterized.expand([("create",), ("update",), ("form_data",)])
+    def test_gamma_denial_does_not_load_provider(self, operation: str) -> None:
+        """A denied Gamma request must return 403 even when its provider is 
down."""
+        self.login("gamma")
+        layer: SemanticLayer = SemanticLayer(name="denied-layer", type="test")
+        view: SemanticView = SemanticView(name="denied-view", 
semantic_layer=layer)
+        db.session.add(view)
+        db.session.commit()
+        chart: Slice | None = None
+        provider: Mock = Mock()
+        provider.get_dimensions.side_effect = RuntimeError("provider 
unavailable")
+        provider.get_metrics.side_effect = RuntimeError("provider unavailable")
+        implementation: PropertyMock
+        try:
+            if operation == "update":
+                editor: Subject = (
+                    db.session.query(Subject)
+                    
.filter_by(user_id=security_manager.find_user(username="gamma").id)
+                    .one()
+                )
+                chart = Slice(
+                    slice_name="gamma-owned-chart",
+                    datasource_id=view.id,
+                    datasource_type="semantic_view",
+                    viz_type="table",
+                    params="{}",
+                    editors=[editor],
+                )
+                db.session.add(chart)
+                db.session.commit()
+            payload: dict[str, Any] = {
+                "slice_name": "denied-save",
+                "datasource_id": view.id,
+                "datasource_type": "semantic_view",
+                "viz_type": "table",
+            }
+            with patch.object(
+                SemanticView,
+                "implementation",
+                new_callable=PropertyMock,
+                return_value=provider,
+            ) as implementation:
+                response: Response
+                if operation == "create":
+                    response = self.client.post("/api/v1/chart/", json=payload)
+                elif operation == "update":
+                    assert chart is not None
+                    response = self.client.put(
+                        f"/api/v1/chart/{chart.id}", json=payload
+                    )
+                else:
+                    response = self.client.post(
+                        "/api/v1/explore/form_data",
+                        json={
+                            "datasource_id": view.id,
+                            "datasource_type": "semantic_view",
+                            "form_data": json.dumps(
+                                {"datasource": f"{view.id}__semantic_view"}
+                            ),
+                        },
+                    )
+            assert response.status_code == 403
+            implementation.assert_not_called()
+            provider.get_dimensions.assert_not_called()
+            provider.get_metrics.assert_not_called()
+            assert (
+                
db.session.query(Slice).filter_by(slice_name="denied-save").count() == 0
+            )
+            if chart is not None:
+                db.session.refresh(chart)
+                assert chart.slice_name == "gamma-owned-chart"
+        finally:
+            db.session.rollback()
+            if chart is not None:
+                db.session.delete(chart)
+            db.session.delete(view)
+            db.session.delete(layer)
+            db.session.commit()
+
     @parameterized.expand([("query",), ("saved_query",)])
     def test_unsavable_datasource_stays_422(self, datasource_type: str) -> 
None:
         """The unsupported-type crash protection from #43500 remains intact."""
diff --git a/tests/unit_tests/security/test_permission_instructions_link.py 
b/tests/unit_tests/security/test_permission_instructions_link.py
index 44f73da8afe..a7da6c3ebf8 100644
--- a/tests/unit_tests/security/test_permission_instructions_link.py
+++ b/tests/unit_tests/security/test_permission_instructions_link.py
@@ -146,7 +146,7 @@ def test_get_datasource_access_link_omits_datasource_name() 
-> None:
     # datasource_name is intentionally not forwarded to prevent name disclosure
     # to users who lack access; the retired placeholder is left literal so a
     # deployment still templating it sees the breakage.
-    ds = MagicMock()
+    ds: MagicMock = MagicMock()
     ds.id = 12
     ds.data = {"id": 12, "name": "Quarterly Sales"}
     with (
@@ -194,7 +194,7 @@ def test_get_table_access_link_joins_table_names() -> None:
 
 
 def test_datasource_error_object_includes_sorted_owner_names() -> None:
-    ds = MagicMock()
+    ds: MagicMock = MagicMock()
     ds.id = 12
     ds.data = {"id": 12, "name": "Quarterly Sales"}
     owner_b, owner_a = MagicMock(), MagicMock()

Reply via email to