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

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

commit 98c0eeccb29c7b80f050f76ce0be717070ddf328
Author: Daniel Vaz Gaspar <[email protected]>
AuthorDate: Wed Mar 5 08:47:49 2025 +0000

    fix: dashboard, chart and dataset import validation (#32500)
    
    (cherry picked from commit fc844d3dfdace890b32c00a507a959b81122b425)
---
 superset/commands/chart/importers/v1/utils.py      |  7 ++-
 superset/commands/dashboard/importers/v1/utils.py  |  7 ++-
 superset/commands/dataset/importers/v1/utils.py    |  8 ++++
 .../charts/commands/importers/v1/import_test.py    | 51 ++++++++++++++++++++-
 .../commands/importers/v1/import_test.py           | 53 +++++++++++++++++++++-
 .../datasets/commands/importers/v1/import_test.py  | 52 +++++++++++++++++++++
 6 files changed, 171 insertions(+), 7 deletions(-)

diff --git a/superset/commands/chart/importers/v1/utils.py 
b/superset/commands/chart/importers/v1/utils.py
index 35a7f6e270..dacef7c1e3 100644
--- a/superset/commands/chart/importers/v1/utils.py
+++ b/superset/commands/chart/importers/v1/utils.py
@@ -50,9 +50,12 @@ def import_chart(
 ) -> Slice:
     can_write = ignore_permissions or security_manager.can_access("can_write", 
"Chart")
     existing = db.session.query(Slice).filter_by(uuid=config["uuid"]).first()
+    user = get_user()
     if existing:
-        if overwrite and can_write and get_user():
-            if not security_manager.can_access_chart(existing):
+        if overwrite and can_write and user:
+            if not security_manager.can_access_chart(existing) or (
+                user not in existing.owners and not security_manager.is_admin()
+            ):
                 raise ImportFailedError(
                     "A chart already exists and user doesn't "
                     "have permissions to overwrite it"
diff --git a/superset/commands/dashboard/importers/v1/utils.py 
b/superset/commands/dashboard/importers/v1/utils.py
index 26cbeb0382..53904890c1 100644
--- a/superset/commands/dashboard/importers/v1/utils.py
+++ b/superset/commands/dashboard/importers/v1/utils.py
@@ -153,9 +153,12 @@ def import_dashboard(  # noqa: C901
         "Dashboard",
     )
     existing = 
db.session.query(Dashboard).filter_by(uuid=config["uuid"]).first()
+    user = get_user()
     if existing:
-        if overwrite and can_write and get_user():
-            if not security_manager.can_access_dashboard(existing):
+        if overwrite and can_write and user:
+            if not security_manager.can_access_dashboard(existing) or (
+                user not in existing.owners and not security_manager.is_admin()
+            ):
                 raise ImportFailedError(
                     "A dashboard already exists and user doesn't "
                     "have permissions to overwrite it"
diff --git a/superset/commands/dataset/importers/v1/utils.py 
b/superset/commands/dataset/importers/v1/utils.py
index f3611e00d9..f4bfc2e079 100644
--- a/superset/commands/dataset/importers/v1/utils.py
+++ b/superset/commands/dataset/importers/v1/utils.py
@@ -113,10 +113,18 @@ def import_dataset(  # noqa: C901
         "Dataset",
     )
     existing = 
db.session.query(SqlaTable).filter_by(uuid=config["uuid"]).first()
+    user = get_user()
     if existing:
+        if overwrite and can_write and user:
+            if user not in existing.owners and not security_manager.is_admin():
+                raise ImportFailedError(
+                    "A dataset already exists and user doesn't "
+                    "have permissions to overwrite it"
+                )
         if not overwrite or not can_write:
             return existing
         config["id"] = existing.id
+
     elif not can_write:
         raise ImportFailedError(
             "Dataset doesn't exist and user doesn't have permission to create 
datasets"
diff --git a/tests/unit_tests/charts/commands/importers/v1/import_test.py 
b/tests/unit_tests/charts/commands/importers/v1/import_test.py
index c31aeb2574..56c4658a84 100644
--- a/tests/unit_tests/charts/commands/importers/v1/import_test.py
+++ b/tests/unit_tests/charts/commands/importers/v1/import_test.py
@@ -181,7 +181,56 @@ def test_import_existing_chart_without_permission(
         .one_or_none()
     )
 
-    with override_user("admin"):
+    user = User(
+        first_name="Alice",
+        last_name="Doe",
+        email="[email protected]",
+        username="admin",
+        roles=[Role(name="Admin")],
+    )
+
+    with override_user(user):
+        with pytest.raises(ImportFailedError) as excinfo:
+            import_chart(chart_config, overwrite=True)
+        assert (
+            str(excinfo.value)
+            == "A chart already exists and user doesn't have permissions to 
overwrite it"  # noqa: E501
+        )
+
+    # Assert that the can write to chart was checked
+    mock_can_access.assert_called_once_with("can_write", "Chart")
+    mock_can_access_chart.assert_called_once_with(slice)
+
+
+def test_import_existing_chart_without_owner_permission(
+    mocker: MockerFixture,
+    session_with_data: Session,
+) -> None:
+    """
+    Test importing a chart when a user doesn't have permissions to modify.
+    """
+    mock_can_access = mocker.patch.object(
+        security_manager, "can_access", return_value=True
+    )
+    mock_can_access_chart = mocker.patch.object(
+        security_manager, "can_access_chart", return_value=True
+    )
+
+    slice = (
+        session_with_data.query(Slice)
+        .filter(Slice.uuid == chart_config["uuid"])
+        .one_or_none()
+    )
+
+    user = User(
+        first_name="Alice",
+        last_name="Doe",
+        email="[email protected]",
+        username="admin",
+        roles=[Role(name="Gamma")],
+    )
+
+    with override_user(user):
         with pytest.raises(ImportFailedError) as excinfo:
             import_chart(chart_config, overwrite=True)
         assert (
diff --git a/tests/unit_tests/dashboards/commands/importers/v1/import_test.py 
b/tests/unit_tests/dashboards/commands/importers/v1/import_test.py
index 09b3eed42d..a1e4a6f87e 100644
--- a/tests/unit_tests/dashboards/commands/importers/v1/import_test.py
+++ b/tests/unit_tests/dashboards/commands/importers/v1/import_test.py
@@ -122,7 +122,7 @@ def test_import_dashboard_without_permission(
     mock_can_access.assert_called_once_with("can_write", "Dashboard")
 
 
-def test_import_existing_dashboard_without_permission(
+def test_import_existing_dashboard_without_access_permission(
     mocker: MockerFixture,
     session_with_data: Session,
 ) -> None:
@@ -142,7 +142,56 @@ def test_import_existing_dashboard_without_permission(
         .one_or_none()
     )
 
-    with override_user("admin"):
+    admin = User(
+        first_name="Alice",
+        last_name="Doe",
+        email="[email protected]",
+        username="admin",
+        roles=[Role(name="Admin")],
+    )
+
+    with override_user(admin):
+        with pytest.raises(ImportFailedError) as excinfo:
+            import_dashboard(dashboard_config, overwrite=True)
+        assert (
+            str(excinfo.value)
+            == "A dashboard already exists and user doesn't have permissions 
to overwrite it"  # noqa: E501
+        )
+
+    # Assert that the can write to dashboard was checked
+    mock_can_access.assert_called_once_with("can_write", "Dashboard")
+    mock_can_access_dashboard.assert_called_once_with(dashboard)
+
+
+def test_import_existing_dashboard_without_owner_permission(
+    mocker: MockerFixture,
+    session_with_data: Session,
+) -> None:
+    """
+    Test importing a dashboard when a user doesn't have ownership and is not 
an Admin.
+    """
+    mock_can_access = mocker.patch.object(
+        security_manager, "can_access", return_value=True
+    )
+    mock_can_access_dashboard = mocker.patch.object(
+        security_manager, "can_access_dashboard", return_value=True
+    )
+
+    dashboard = (
+        session_with_data.query(Dashboard)
+        .filter(Dashboard.uuid == dashboard_config["uuid"])
+        .one_or_none()
+    )
+
+    user = User(
+        first_name="Alice",
+        last_name="Doe",
+        email="[email protected]",
+        username="admin",
+        roles=[Role(name="Gamma")],
+    )
+
+    with override_user(user):
         with pytest.raises(ImportFailedError) as excinfo:
             import_dashboard(dashboard_config, overwrite=True)
         assert (
diff --git a/tests/unit_tests/datasets/commands/importers/v1/import_test.py 
b/tests/unit_tests/datasets/commands/importers/v1/import_test.py
index 2a2038046d..6bba6f039d 100644
--- a/tests/unit_tests/datasets/commands/importers/v1/import_test.py
+++ b/tests/unit_tests/datasets/commands/importers/v1/import_test.py
@@ -24,6 +24,7 @@ from unittest.mock import Mock, patch
 
 import pytest
 from flask import current_app
+from flask_appbuilder.security.sqla.models import Role, User
 from pytest_mock import MockerFixture
 from sqlalchemy.orm.session import Session
 
@@ -32,7 +33,9 @@ from superset.commands.dataset.exceptions import (
     DatasetForbiddenDataURI,
 )
 from superset.commands.dataset.importers.v1.utils import validate_data_uri
+from superset.commands.exceptions import ImportFailedError
 from superset.utils import json
+from superset.utils.core import override_user
 
 
 def test_import_dataset(mocker: MockerFixture, session: Session) -> None:
@@ -536,6 +539,55 @@ def test_import_dataset_managed_externally(
     assert sqla_table.external_url == "https://example.org/my_table";
 
 
+def test_import_dataset_without_owner_permission(
+    mocker: MockerFixture,
+    session: Session,
+) -> None:
+    """
+    Test importing a dataset that is managed externally.
+    """
+    from superset import security_manager
+    from superset.commands.dataset.importers.v1.utils import import_dataset
+    from superset.connectors.sqla.models import SqlaTable
+    from superset.models.core import Database
+    from tests.integration_tests.fixtures.importexport import dataset_config
+
+    mock_can_access = mocker.patch.object(
+        security_manager, "can_access", return_value=True
+    )
+
+    engine = db.session.get_bind()
+    SqlaTable.metadata.create_all(engine)  # pylint: disable=no-member
+
+    database = Database(database_name="my_database", 
sqlalchemy_uri="sqlite://")
+    db.session.add(database)
+    db.session.flush()
+
+    config = copy.deepcopy(dataset_config)
+    config["database_id"] = database.id
+
+    import_dataset(config)
+    user = User(
+        first_name="Alice",
+        last_name="Doe",
+        email="[email protected]",
+        username="admin",
+        roles=[Role(name="Gamma")],
+    )
+
+    with override_user(user):
+        with pytest.raises(ImportFailedError) as excinfo:
+            import_dataset(config, overwrite=True)
+
+        assert (
+            str(excinfo.value)
+            == "A dataset already exists and user doesn't have permissions to 
overwrite it"  # noqa: E501
+        )
+
+    # Assert that the can write to chart was checked
+    mock_can_access.assert_called_with("can_write", "Dataset")
+
+
 @pytest.mark.parametrize(
     "allowed_urls, data_uri, expected, exception_class",
     [

Reply via email to