Skip to content

Commit fc844d3

Browse files
authored
fix: dashboard, chart and dataset import validation (#32500)
1 parent d8686c2 commit fc844d3

File tree

6 files changed

+171
-7
lines changed

6 files changed

+171
-7
lines changed

superset/commands/chart/importers/v1/utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,12 @@ def import_chart(
5050
) -> Slice:
5151
can_write = ignore_permissions or security_manager.can_access("can_write", "Chart")
5252
existing = db.session.query(Slice).filter_by(uuid=config["uuid"]).first()
53+
user = get_user()
5354
if existing:
54-
if overwrite and can_write and get_user():
55-
if not security_manager.can_access_chart(existing):
55+
if overwrite and can_write and user:
56+
if not security_manager.can_access_chart(existing) or (
57+
user not in existing.owners and not security_manager.is_admin()
58+
):
5659
raise ImportFailedError(
5760
"A chart already exists and user doesn't "
5861
"have permissions to overwrite it"

superset/commands/dashboard/importers/v1/utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,12 @@ def import_dashboard( # noqa: C901
153153
"Dashboard",
154154
)
155155
existing = db.session.query(Dashboard).filter_by(uuid=config["uuid"]).first()
156+
user = get_user()
156157
if existing:
157-
if overwrite and can_write and get_user():
158-
if not security_manager.can_access_dashboard(existing):
158+
if overwrite and can_write and user:
159+
if not security_manager.can_access_dashboard(existing) or (
160+
user not in existing.owners and not security_manager.is_admin()
161+
):
159162
raise ImportFailedError(
160163
"A dashboard already exists and user doesn't "
161164
"have permissions to overwrite it"

superset/commands/dataset/importers/v1/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,18 @@ def import_dataset( # noqa: C901
113113
"Dataset",
114114
)
115115
existing = db.session.query(SqlaTable).filter_by(uuid=config["uuid"]).first()
116+
user = get_user()
116117
if existing:
118+
if overwrite and can_write and user:
119+
if user not in existing.owners and not security_manager.is_admin():
120+
raise ImportFailedError(
121+
"A dataset already exists and user doesn't "
122+
"have permissions to overwrite it"
123+
)
117124
if not overwrite or not can_write:
118125
return existing
119126
config["id"] = existing.id
127+
120128
elif not can_write:
121129
raise ImportFailedError(
122130
"Dataset doesn't exist and user doesn't have permission to create datasets"

tests/unit_tests/charts/commands/importers/v1/import_test.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,56 @@ def test_import_existing_chart_without_permission(
181181
.one_or_none()
182182
)
183183

184-
with override_user("admin"):
184+
user = User(
185+
first_name="Alice",
186+
last_name="Doe",
187+
188+
username="admin",
189+
roles=[Role(name="Admin")],
190+
)
191+
192+
with override_user(user):
193+
with pytest.raises(ImportFailedError) as excinfo:
194+
import_chart(chart_config, overwrite=True)
195+
assert (
196+
str(excinfo.value)
197+
== "A chart already exists and user doesn't have permissions to overwrite it" # noqa: E501
198+
)
199+
200+
# Assert that the can write to chart was checked
201+
mock_can_access.assert_called_once_with("can_write", "Chart")
202+
mock_can_access_chart.assert_called_once_with(slice)
203+
204+
205+
def test_import_existing_chart_without_owner_permission(
206+
mocker: MockerFixture,
207+
session_with_data: Session,
208+
) -> None:
209+
"""
210+
Test importing a chart when a user doesn't have permissions to modify.
211+
"""
212+
mock_can_access = mocker.patch.object(
213+
security_manager, "can_access", return_value=True
214+
)
215+
mock_can_access_chart = mocker.patch.object(
216+
security_manager, "can_access_chart", return_value=True
217+
)
218+
219+
slice = (
220+
session_with_data.query(Slice)
221+
.filter(Slice.uuid == chart_config["uuid"])
222+
.one_or_none()
223+
)
224+
225+
user = User(
226+
first_name="Alice",
227+
last_name="Doe",
228+
229+
username="admin",
230+
roles=[Role(name="Gamma")],
231+
)
232+
233+
with override_user(user):
185234
with pytest.raises(ImportFailedError) as excinfo:
186235
import_chart(chart_config, overwrite=True)
187236
assert (

tests/unit_tests/dashboards/commands/importers/v1/import_test.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def test_import_dashboard_without_permission(
122122
mock_can_access.assert_called_once_with("can_write", "Dashboard")
123123

124124

125-
def test_import_existing_dashboard_without_permission(
125+
def test_import_existing_dashboard_without_access_permission(
126126
mocker: MockerFixture,
127127
session_with_data: Session,
128128
) -> None:
@@ -142,7 +142,56 @@ def test_import_existing_dashboard_without_permission(
142142
.one_or_none()
143143
)
144144

145-
with override_user("admin"):
145+
admin = User(
146+
first_name="Alice",
147+
last_name="Doe",
148+
149+
username="admin",
150+
roles=[Role(name="Admin")],
151+
)
152+
153+
with override_user(admin):
154+
with pytest.raises(ImportFailedError) as excinfo:
155+
import_dashboard(dashboard_config, overwrite=True)
156+
assert (
157+
str(excinfo.value)
158+
== "A dashboard already exists and user doesn't have permissions to overwrite it" # noqa: E501
159+
)
160+
161+
# Assert that the can write to dashboard was checked
162+
mock_can_access.assert_called_once_with("can_write", "Dashboard")
163+
mock_can_access_dashboard.assert_called_once_with(dashboard)
164+
165+
166+
def test_import_existing_dashboard_without_owner_permission(
167+
mocker: MockerFixture,
168+
session_with_data: Session,
169+
) -> None:
170+
"""
171+
Test importing a dashboard when a user doesn't have ownership and is not an Admin.
172+
"""
173+
mock_can_access = mocker.patch.object(
174+
security_manager, "can_access", return_value=True
175+
)
176+
mock_can_access_dashboard = mocker.patch.object(
177+
security_manager, "can_access_dashboard", return_value=True
178+
)
179+
180+
dashboard = (
181+
session_with_data.query(Dashboard)
182+
.filter(Dashboard.uuid == dashboard_config["uuid"])
183+
.one_or_none()
184+
)
185+
186+
user = User(
187+
first_name="Alice",
188+
last_name="Doe",
189+
190+
username="admin",
191+
roles=[Role(name="Gamma")],
192+
)
193+
194+
with override_user(user):
146195
with pytest.raises(ImportFailedError) as excinfo:
147196
import_dashboard(dashboard_config, overwrite=True)
148197
assert (

tests/unit_tests/datasets/commands/importers/v1/import_test.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import pytest
2626
from flask import current_app
27+
from flask_appbuilder.security.sqla.models import Role, User
2728
from pytest_mock import MockerFixture
2829
from sqlalchemy.orm.session import Session
2930

@@ -32,7 +33,9 @@
3233
DatasetForbiddenDataURI,
3334
)
3435
from superset.commands.dataset.importers.v1.utils import validate_data_uri
36+
from superset.commands.exceptions import ImportFailedError
3537
from superset.utils import json
38+
from superset.utils.core import override_user
3639

3740

3841
def test_import_dataset(mocker: MockerFixture, session: Session) -> None:
@@ -536,6 +539,55 @@ def test_import_dataset_managed_externally(
536539
assert sqla_table.external_url == "https://example.org/my_table"
537540

538541

542+
def test_import_dataset_without_owner_permission(
543+
mocker: MockerFixture,
544+
session: Session,
545+
) -> None:
546+
"""
547+
Test importing a dataset that is managed externally.
548+
"""
549+
from superset import security_manager
550+
from superset.commands.dataset.importers.v1.utils import import_dataset
551+
from superset.connectors.sqla.models import SqlaTable
552+
from superset.models.core import Database
553+
from tests.integration_tests.fixtures.importexport import dataset_config
554+
555+
mock_can_access = mocker.patch.object(
556+
security_manager, "can_access", return_value=True
557+
)
558+
559+
engine = db.session.get_bind()
560+
SqlaTable.metadata.create_all(engine) # pylint: disable=no-member
561+
562+
database = Database(database_name="my_database", sqlalchemy_uri="sqlite://")
563+
db.session.add(database)
564+
db.session.flush()
565+
566+
config = copy.deepcopy(dataset_config)
567+
config["database_id"] = database.id
568+
569+
import_dataset(config)
570+
user = User(
571+
first_name="Alice",
572+
last_name="Doe",
573+
574+
username="admin",
575+
roles=[Role(name="Gamma")],
576+
)
577+
578+
with override_user(user):
579+
with pytest.raises(ImportFailedError) as excinfo:
580+
import_dataset(config, overwrite=True)
581+
582+
assert (
583+
str(excinfo.value)
584+
== "A dataset already exists and user doesn't have permissions to overwrite it" # noqa: E501
585+
)
586+
587+
# Assert that the can write to chart was checked
588+
mock_can_access.assert_called_with("can_write", "Dataset")
589+
590+
539591
@pytest.mark.parametrize(
540592
"allowed_urls, data_uri, expected, exception_class",
541593
[

0 commit comments

Comments
 (0)