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 2021/06/23 14:00:51 UTC
[superset] branch master updated: fix: datasource payload is
incorrect (#15184)
This is an automated email from the ASF dual-hosted git repository.
beto 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 216e2b8 fix: datasource payload is incorrect (#15184)
216e2b8 is described below
commit 216e2b8e8e24132cc4cab072c20bde56259f1116
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Jun 23 06:59:34 2021 -0700
fix: datasource payload is incorrect (#15184)
* fix: datasource payload is incorrect
* Add tests, clean code
---
superset/connectors/connector_registry.py | 36 ++++++++++++++
superset/views/chart/views.py | 2 +-
superset/views/core.py | 2 +-
tests/access_tests.py | 79 +++++++++++++++++++++++++++++++
4 files changed, 117 insertions(+), 2 deletions(-)
diff --git a/superset/connectors/connector_registry.py b/superset/connectors/connector_registry.py
index 86081cd..7de19b3 100644
--- a/superset/connectors/connector_registry.py
+++ b/superset/connectors/connector_registry.py
@@ -14,6 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
+from collections import defaultdict
from typing import Dict, List, Optional, Set, Type, TYPE_CHECKING
from flask_babel import _
@@ -100,6 +101,41 @@ class ConnectorRegistry:
raise NoResultFound(_("Datasource id not found: %(id)s", id=datasource_id))
@classmethod
+ def get_user_datasources(cls, session: Session) -> List["BaseDatasource"]:
+ from superset import security_manager
+
+ # collect datasources which the user has explicit permissions to
+ user_perms = security_manager.user_view_menu_names("datasource_access")
+ schema_perms = security_manager.user_view_menu_names("schema_access")
+ user_datasources = set()
+ for datasource_class in ConnectorRegistry.sources.values():
+ user_datasources.update(
+ session.query(datasource_class)
+ .filter(
+ or_(
+ datasource_class.perm.in_(user_perms),
+ datasource_class.schema_perm.in_(schema_perms),
+ )
+ )
+ .all()
+ )
+
+ # group all datasources by database
+ all_datasources = cls.get_all_datasources(session)
+ datasources_by_database: Dict["Database", Set["BaseDatasource"]] = defaultdict(
+ set
+ )
+ for datasource in all_datasources:
+ datasources_by_database[datasource.database].add(datasource)
+
+ # add datasources with implicit permission (eg, database access)
+ for database, datasources in datasources_by_database.items():
+ if security_manager.can_access_database(database):
+ user_datasources.update(datasources)
+
+ return list(user_datasources)
+
+ @classmethod
def get_datasource_by_name( # pylint: disable=too-many-arguments
cls,
session: Session,
diff --git a/superset/views/chart/views.py b/superset/views/chart/views.py
index e4fedac..45dbd23 100644
--- a/superset/views/chart/views.py
+++ b/superset/views/chart/views.py
@@ -65,7 +65,7 @@ class SliceModelView(
def add(self) -> FlaskResponse:
datasources = [
{"value": str(d.id) + "__" + d.type, "label": repr(d)}
- for d in ConnectorRegistry.get_all_datasources(db.session)
+ for d in ConnectorRegistry.get_user_datasources(db.session)
]
payload = {
"datasources": sorted(
diff --git a/superset/views/core.py b/superset/views/core.py
index b04def7..6efda99 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -184,7 +184,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
sorted(
[
datasource.short_data
- for datasource in ConnectorRegistry.get_all_datasources(db.session)
+ for datasource in ConnectorRegistry.get_user_datasources(db.session)
if datasource.short_data.get("name")
],
key=lambda datasource: datasource["name"],
diff --git a/tests/access_tests.py b/tests/access_tests.py
index d3cc55a..795ca98 100644
--- a/tests/access_tests.py
+++ b/tests/access_tests.py
@@ -18,6 +18,7 @@
"""Unit tests for Superset"""
import json
import unittest
+from collections import namedtuple
from unittest import mock
from tests.fixtures.birth_names_dashboard import load_birth_names_dashboard_with_slices
@@ -626,5 +627,83 @@ class TestRequestAccess(SupersetTestCase):
session.commit()
+class TestDatasources(SupersetTestCase):
+ def test_get_user_datasources_admin(self):
+ Datasource = namedtuple("Datasource", ["database", "schema", "name"])
+
+ mock_session = mock.MagicMock()
+ mock_session.query.return_value.filter.return_value.all.return_value = []
+
+ with mock.patch("superset.security_manager") as mock_security_manager:
+ mock_security_manager.can_access_database.return_value = True
+
+ with mock.patch.object(
+ ConnectorRegistry, "get_all_datasources"
+ ) as mock_get_all_datasources:
+ mock_get_all_datasources.return_value = [
+ Datasource("database1", "schema1", "table1"),
+ Datasource("database1", "schema1", "table2"),
+ Datasource("database2", None, "table1"),
+ ]
+
+ datasources = ConnectorRegistry.get_user_datasources(mock_session)
+
+ assert sorted(datasources) == [
+ Datasource("database1", "schema1", "table1"),
+ Datasource("database1", "schema1", "table2"),
+ Datasource("database2", None, "table1"),
+ ]
+
+ def test_get_user_datasources_gamma(self):
+ Datasource = namedtuple("Datasource", ["database", "schema", "name"])
+
+ mock_session = mock.MagicMock()
+ mock_session.query.return_value.filter.return_value.all.return_value = []
+
+ with mock.patch("superset.security_manager") as mock_security_manager:
+ mock_security_manager.can_access_database.return_value = False
+
+ with mock.patch.object(
+ ConnectorRegistry, "get_all_datasources"
+ ) as mock_get_all_datasources:
+ mock_get_all_datasources.return_value = [
+ Datasource("database1", "schema1", "table1"),
+ Datasource("database1", "schema1", "table2"),
+ Datasource("database2", None, "table1"),
+ ]
+
+ datasources = ConnectorRegistry.get_user_datasources(mock_session)
+
+ assert datasources == []
+
+ def test_get_user_datasources_gamma_with_schema(self):
+ Datasource = namedtuple("Datasource", ["database", "schema", "name"])
+
+ mock_session = mock.MagicMock()
+ mock_session.query.return_value.filter.return_value.all.return_value = [
+ Datasource("database1", "schema1", "table1"),
+ Datasource("database1", "schema1", "table2"),
+ ]
+
+ with mock.patch("superset.security_manager") as mock_security_manager:
+ mock_security_manager.can_access_database.return_value = False
+
+ with mock.patch.object(
+ ConnectorRegistry, "get_all_datasources"
+ ) as mock_get_all_datasources:
+ mock_get_all_datasources.return_value = [
+ Datasource("database1", "schema1", "table1"),
+ Datasource("database1", "schema1", "table2"),
+ Datasource("database2", None, "table1"),
+ ]
+
+ datasources = ConnectorRegistry.get_user_datasources(mock_session)
+
+ assert sorted(datasources) == [
+ Datasource("database1", "schema1", "table1"),
+ Datasource("database1", "schema1", "table2"),
+ ]
+
+
if __name__ == "__main__":
unittest.main()