You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2024/01/18 20:35:04 UTC
(superset) 01/01: feat: prevent adhoc metrics from guest user
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch supersetsec-41
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 7bdc483c17685f9d886ec57f4e61ba4604a55e19
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Jan 18 15:33:13 2024 -0500
feat: prevent adhoc metrics from guest user
---
superset/common/query_object.py | 17 ++++++-
tests/unit_tests/queries/query_object_test.py | 73 ++++++++++++++++++++++++++-
2 files changed, 87 insertions(+), 3 deletions(-)
diff --git a/superset/common/query_object.py b/superset/common/query_object.py
index 989df5775b..226d81debb 100644
--- a/superset/common/query_object.py
+++ b/superset/common/query_object.py
@@ -27,12 +27,14 @@ from flask import g
from flask_babel import gettext as _
from pandas import DataFrame
-from superset import feature_flag_manager
+from superset import feature_flag_manager, security_manager
from superset.common.chart_data import ChartDataResultType
+from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
from superset.exceptions import (
InvalidPostProcessingError,
QueryClauseValidationException,
QueryObjectValidationError,
+ SupersetSecurityException,
)
from superset.sql_parse import sanitize_clause
from superset.superset_typing import Column, Metric, OrderBy
@@ -189,6 +191,19 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
def is_str_or_adhoc(metric: Metric) -> bool:
return isinstance(metric, str) or is_adhoc_metric(metric)
+ # When connecting with a guest token users should only be allowed to use
+ # predefined metrics.
+ if security_manager.is_guest_user() and any(
+ is_adhoc_metric(metric) for metric in metrics or []
+ ):
+ raise SupersetSecurityException(
+ SupersetError(
+ error_type=SupersetErrorType.DASHBOARD_SECURITY_ACCESS_ERROR,
+ message="Guest users can only use predefined metrics",
+ level=ErrorLevel.ERROR,
+ ),
+ )
+
self.metrics = metrics and [
x if is_str_or_adhoc(x) else x["label"] for x in metrics # type: ignore
]
diff --git a/tests/unit_tests/queries/query_object_test.py b/tests/unit_tests/queries/query_object_test.py
index 81a654653f..be12e795a2 100644
--- a/tests/unit_tests/queries/query_object_test.py
+++ b/tests/unit_tests/queries/query_object_test.py
@@ -16,13 +16,17 @@
# under the License.
from unittest.mock import call, patch
+import pytest
from flask_appbuilder.security.sqla.models import User
-from pytest_mock import MockFixture
+from pytest_mock import MockerFixture
from superset.common.query_object import QueryObject
from superset.connectors.sqla.models import SqlaTable
+from superset.errors import SupersetErrorType
+from superset.exceptions import SupersetSecurityException
from superset.models.core import Database
-from superset.utils.core import override_user
+from superset.utils.core import AdhocMetric, override_user
+from tests.unit_tests.conftest import with_feature_flags
def cache_impersonation_flag_side_effect(feature=None):
@@ -343,3 +347,68 @@ def test_cache_key_cache_impersonation_on_with_different_user_and_db_impersonati
),
]
)
+
+
+def test_query_object_metrics():
+ """
+ Tests that metrics are correctly parsed from the query object.
+ """
+ query_object = QueryObject(row_limit=1, metrics=["sum__SP_POP_TOTL"])
+ assert query_object.metrics == ["sum__SP_POP_TOTL"]
+
+ query_object = QueryObject(row_limit=1, metrics=[{"label": "sum__SP_POP_TOTL"}])
+ assert query_object.metrics == ["sum__SP_POP_TOTL"]
+
+ query_object = QueryObject(
+ row_limit=1,
+ metrics=[
+ {
+ "aggregate": "SUM",
+ "column": None,
+ "expressionType": "SQL",
+ "hasCustomLabel": False,
+ "label": 'sum("SP_POP_TOTL")',
+ "sqlExpression": 'sum("SP_POP_TOTL")',
+ }
+ ],
+ )
+ assert query_object.metrics == [
+ {
+ "aggregate": "SUM",
+ "column": None,
+ "expressionType": "SQL",
+ "hasCustomLabel": False,
+ "label": 'sum("SP_POP_TOTL")',
+ "sqlExpression": 'sum("SP_POP_TOTL")',
+ },
+ ]
+
+
+@with_feature_flags(EMBEDDED_SUPERSET=True)
+def test_query_object_embedded(mocker: MockerFixture):
+ """
+ Test that only predefined metrics are allowed in embedded dashboards.
+ """
+ g = mocker.patch("superset.security.manager.g")
+ g.user.is_guest_user = True
+
+ query_object = QueryObject(row_limit=1, metrics=["sum__SP_POP_TOTL"])
+ assert query_object.metrics == ["sum__SP_POP_TOTL"]
+
+ query_object = QueryObject(row_limit=1, metrics=[{"label": "sum__SP_POP_TOTL"}])
+ assert query_object.metrics == ["sum__SP_POP_TOTL"]
+
+ adhoc_metric: AdhocMetric = {
+ "aggregate": "SUM",
+ "column": None,
+ "expressionType": "SQL",
+ "hasCustomLabel": False,
+ "label": 'sum("SP_POP_TOTL")',
+ "sqlExpression": 'sum("SP_POP_TOTL")',
+ }
+ with pytest.raises(SupersetSecurityException) as excinfo:
+ QueryObject(row_limit=1, metrics=[adhoc_metric])
+ assert (
+ excinfo.value.error.error_type
+ == SupersetErrorType.DASHBOARD_SECURITY_ACCESS_ERROR
+ )