diff --git a/tests/unit_tests/security/manager_test.py b/tests/unit_tests/security/manager_test.py index 22ec0dda4a7a4..893e47884e4b0 100644 --- a/tests/unit_tests/security/manager_test.py +++ b/tests/unit_tests/security/manager_test.py @@ -26,7 +26,7 @@ from superset.exceptions import SupersetSecurityException from superset.extensions import appbuilder from superset.models.slice import Slice -from superset.security.manager import SupersetSecurityManager +from superset.security.manager import query_context_modified, SupersetSecurityManager from superset.sql_parse import Table from superset.superset_typing import AdhocMetric from superset.utils.core import override_user @@ -414,3 +414,84 @@ def test_raise_for_access_chart_owner( sm.raise_for_access( chart=slice, ) + + +def test_query_context_modified( + mocker: MockFixture, + stored_metrics: list[AdhocMetric], +) -> None: + """ + Test the `query_context_modified` function. + + The function is used to ensure guest users are not modifying the request payload on + embedded dashboard, preventing users from modifying it to access metrics different + from the ones stored in dashboard charts. + """ + query_context = mocker.MagicMock() + query_context.slice_.id = 42 + query_context.slice_.query_context = None + query_context.slice_.params_dict = { + "metrics": stored_metrics, + } + + query_context.form_data = { + "slice_id": 42, + "metrics": stored_metrics, + } + query_context.queries = [QueryObject(metrics=stored_metrics)] # type: ignore + assert not query_context_modified(query_context) + + +def test_query_context_modified_native_filter(mocker: MockFixture) -> None: + """ + Test the `query_context_modified` function with a native filter request. + + A native filter request has no chart (slice) associated with it. + """ + query_context = mocker.MagicMock() + query_context.slice_ = None + + assert not query_context_modified(query_context) + + +def test_query_context_modified_mixed_chart(mocker: MockFixture) -> None: + """ + Test the `query_context_modified` function for a mixed chart request. + + The metrics in the mixed chart are a nested dictionary (due to `columns`), and need + to be serialized to JSON with the keys sorted in order to compare the request + metrics with the chart metrics. + """ + stored_metrics = [ + { + "optionName": "metric_vgops097wej_g8uff99zhk7", + "label": "AVG(num)", + "expressionType": "SIMPLE", + "column": {"column_name": "num", "type": "BIGINT(20)"}, + "aggregate": "AVG", + } + ] + # different order (remember, dicts have order!) + requested_metrics = [ + { + "aggregate": "AVG", + "column": {"column_name": "num", "type": "BIGINT(20)"}, + "expressionType": "SIMPLE", + "label": "AVG(num)", + "optionName": "metric_vgops097wej_g8uff99zhk7", + } + ] + + query_context = mocker.MagicMock() + query_context.slice_.id = 42 + query_context.slice_.query_context = None + query_context.slice_.params_dict = { + "metrics": stored_metrics, + } + + query_context.form_data = { + "slice_id": 42, + "metrics": requested_metrics, + } + query_context.queries = [QueryObject(metrics=requested_metrics)] # type: ignore + assert not query_context_modified(query_context)