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

michaelsmolina 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 f9109583ce fix: Allow dataset owners to explore their datasets (#20382)
f9109583ce is described below

commit f9109583ce1ede0cb2b9f4ad09452bba552a55ce
Author: Reese <[email protected]>
AuthorDate: Wed Jul 6 10:27:50 2022 -0400

    fix: Allow dataset owners to explore their datasets (#20382)
    
    * fix: Allow dataset owners to explore their datasets
    
    * Re-order imports
    
    * Give owners security manager permissions to their datasets
    
    * Update test suite
    
    * Add SqlaTable to is_owner types
    
    * Add owners to datasource mock
    
    * Fix VSCode import error
    
    * Fix merge error
---
 superset/charts/filters.py                | 17 +++++++++++++++--
 superset/security/manager.py              |  6 +++++-
 superset/views/utils.py                   |  3 ++-
 tests/integration_tests/base_tests.py     |  5 +++--
 tests/integration_tests/security_tests.py | 10 ++++++++--
 tests/unit_tests/explore/utils_test.py    |  6 ++++--
 6 files changed, 37 insertions(+), 10 deletions(-)

diff --git a/superset/charts/filters.py b/superset/charts/filters.py
index 9ea3050903..c60d285940 100644
--- a/superset/charts/filters.py
+++ b/superset/charts/filters.py
@@ -20,7 +20,8 @@ from flask_babel import lazy_gettext as _
 from sqlalchemy import and_, or_
 from sqlalchemy.orm.query import Query
 
-from superset import security_manager
+from superset import db, security_manager
+from superset.connectors.sqla import models
 from superset.connectors.sqla.models import SqlaTable
 from superset.models.slice import Slice
 from superset.views.base import BaseFilter
@@ -77,6 +78,18 @@ class ChartFilter(BaseFilter):  # pylint: 
disable=too-few-public-methods
             return query
         perms = security_manager.user_view_menu_names("datasource_access")
         schema_perms = security_manager.user_view_menu_names("schema_access")
+        owner_ids_query = (
+            db.session.query(models.SqlaTable.id)
+            .join(models.SqlaTable.owners)
+            .filter(
+                security_manager.user_model.id
+                == security_manager.user_model.get_user_id()
+            )
+        )
         return query.filter(
-            or_(self.model.perm.in_(perms), 
self.model.schema_perm.in_(schema_perms))
+            or_(
+                self.model.perm.in_(perms),
+                self.model.schema_perm.in_(schema_perms),
+                models.SqlaTable.id.in_(owner_ids_query),
+            )
         )
diff --git a/superset/security/manager.py b/superset/security/manager.py
index 675cb63f1e..78c83f72eb 100644
--- a/superset/security/manager.py
+++ b/superset/security/manager.py
@@ -1093,6 +1093,7 @@ class SupersetSecurityManager(  # pylint: 
disable=too-many-public-methods
         from superset.connectors.sqla.models import SqlaTable
         from superset.extensions import feature_flag_manager
         from superset.sql_parse import Table
+        from superset.views.utils import is_owner
 
         if database and table or query:
             if query:
@@ -1123,7 +1124,9 @@ class SupersetSecurityManager(  # pylint: 
disable=too-many-public-methods
 
                     # Access to any datasource is suffice.
                     for datasource_ in datasources:
-                        if self.can_access("datasource_access", 
datasource_.perm):
+                        if self.can_access(
+                            "datasource_access", datasource_.perm
+                        ) or is_owner(datasource_, getattr(g, "user", None)):
                             break
                     else:
                         denied.add(table_)
@@ -1149,6 +1152,7 @@ class SupersetSecurityManager(  # pylint: 
disable=too-many-public-methods
             if not (
                 self.can_access_schema(datasource)
                 or self.can_access("datasource_access", datasource.perm or "")
+                or is_owner(datasource, getattr(g, "user", None))
                 or (
                     should_check_dashboard_access
                     and self.can_access_based_on_dashboard(datasource)
diff --git a/superset/views/utils.py b/superset/views/utils.py
index 94f595ce18..d696b4b744 100644
--- a/superset/views/utils.py
+++ b/superset/views/utils.py
@@ -32,6 +32,7 @@ from sqlalchemy.orm.exc import NoResultFound
 import superset.models.core as models
 from superset import app, dataframe, db, result_set, viz
 from superset.common.db_query_status import QueryStatus
+from superset.connectors.sqla.models import SqlaTable
 from superset.datasource.dao import DatasourceDAO
 from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
 from superset.exceptions import (
@@ -426,7 +427,7 @@ def is_slice_in_container(
     return False
 
 
-def is_owner(obj: Union[Dashboard, Slice], user: User) -> bool:
+def is_owner(obj: Union[Dashboard, Slice, SqlaTable], user: User) -> bool:
     """Check if user is owner of the slice"""
     return obj and user in obj.owners
 
diff --git a/tests/integration_tests/base_tests.py 
b/tests/integration_tests/base_tests.py
index ebad0dffd6..280d58774a 100644
--- a/tests/integration_tests/base_tests.py
+++ b/tests/integration_tests/base_tests.py
@@ -21,7 +21,7 @@ import imp
 import json
 from contextlib import contextmanager
 from typing import Any, Dict, Union, List, Optional
-from unittest.mock import Mock, patch
+from unittest.mock import Mock, patch, MagicMock
 
 import pandas as pd
 import pytest
@@ -252,7 +252,7 @@ class SupersetTestCase(TestCase):
 
     @staticmethod
     def get_datasource_mock() -> BaseDatasource:
-        datasource = Mock()
+        datasource = MagicMock()
         results = Mock()
         results.query = Mock()
         results.status = Mock()
@@ -266,6 +266,7 @@ class SupersetTestCase(TestCase):
         datasource.database = Mock()
         datasource.database.db_engine_spec = Mock()
         datasource.database.db_engine_spec.mutate_expression_label = lambda x: 
x
+        datasource.owners = MagicMock()
         return datasource
 
     def get_resp(
diff --git a/tests/integration_tests/security_tests.py 
b/tests/integration_tests/security_tests.py
index 6311b3b982..81781d16c5 100644
--- a/tests/integration_tests/security_tests.py
+++ b/tests/integration_tests/security_tests.py
@@ -906,7 +906,10 @@ class TestSecurityManager(SupersetTestCase):
 
     @patch("superset.security.SupersetSecurityManager.can_access")
     @patch("superset.security.SupersetSecurityManager.can_access_schema")
-    def test_raise_for_access_datasource(self, mock_can_access_schema, 
mock_can_access):
+    @patch("superset.views.utils.is_owner")
+    def test_raise_for_access_datasource(
+        self, mock_can_access_schema, mock_can_access, mock_is_owner
+    ):
         datasource = self.get_datasource_mock()
 
         mock_can_access_schema.return_value = True
@@ -914,12 +917,14 @@ class TestSecurityManager(SupersetTestCase):
 
         mock_can_access.return_value = False
         mock_can_access_schema.return_value = False
+        mock_is_owner.return_value = False
 
         with self.assertRaises(SupersetSecurityException):
             security_manager.raise_for_access(datasource=datasource)
 
     @patch("superset.security.SupersetSecurityManager.can_access")
-    def test_raise_for_access_query(self, mock_can_access):
+    @patch("superset.views.utils.is_owner")
+    def test_raise_for_access_query(self, mock_can_access, mock_is_owner):
         query = Mock(
             database=get_example_database(), schema="bar", sql="SELECT * FROM 
foo"
         )
@@ -928,6 +933,7 @@ class TestSecurityManager(SupersetTestCase):
         security_manager.raise_for_access(query=query)
 
         mock_can_access.return_value = False
+        mock_is_owner.return_value = False
 
         with self.assertRaises(SupersetSecurityException):
             security_manager.raise_for_access(query=query)
diff --git a/tests/unit_tests/explore/utils_test.py 
b/tests/unit_tests/explore/utils_test.py
index 9ef9287217..64aefbf43a 100644
--- a/tests/unit_tests/explore/utils_test.py
+++ b/tests/unit_tests/explore/utils_test.py
@@ -271,7 +271,7 @@ def test_query_has_access(mocker: MockFixture, app_context: 
AppContext) -> None:
     )
 
 
-def test_query_no_access(mocker: MockFixture, app_context: AppContext) -> None:
+def test_query_no_access(mocker: MockFixture, client, app_context: AppContext) 
-> None:
     from superset.connectors.sqla.models import SqlaTable
     from superset.explore.utils import check_datasource_access
     from superset.models.core import Database
@@ -282,7 +282,9 @@ def test_query_no_access(mocker: MockFixture, app_context: 
AppContext) -> None:
             query_find_by_id,
             return_value=Query(database=Database(), sql="select * from foo"),
         )
-        mocker.patch(query_datasources_by_name, return_value=[SqlaTable()])
+        table = SqlaTable()
+        table.owners = []
+        mocker.patch(query_datasources_by_name, return_value=[table])
         mocker.patch(is_user_admin, return_value=False)
         mocker.patch(is_owner, return_value=False)
         mocker.patch(can_access, return_value=False)

Reply via email to