diff --git a/superset-frontend/src/views/CRUD/chart/ChartCard.tsx b/superset-frontend/src/views/CRUD/chart/ChartCard.tsx index c387ca7253..40eb109974 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartCard.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartCard.tsx @@ -66,7 +66,7 @@ export default function ChartCard({ const canEdit = hasPerm('can_write'); const canDelete = hasPerm('can_write'); const canExport = - hasPerm('can_read') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); + hasPerm('can_export') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); const theme = useTheme(); const menu = ( diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index ab7c1bd371..6982f784bb 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -183,7 +183,7 @@ function ChartList(props: ChartListProps) { const canEdit = hasPerm('can_write'); const canDelete = hasPerm('can_write'); const canExport = - hasPerm('can_read') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); + hasPerm('can_export') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }]; const handleBulkChartExport = (chartsToExport: Chart[]) => { diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx index b51ec1d10b..3bd28667f8 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardCard.tsx @@ -66,7 +66,7 @@ function DashboardCard({ const history = useHistory(); const canEdit = hasPerm('can_write'); const canDelete = hasPerm('can_write'); - const canExport = hasPerm('can_read'); + const canExport = hasPerm('can_export'); const theme = useTheme(); const menu = ( diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index 59a14b754b..6d4ba8b484 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -149,7 +149,7 @@ function DashboardList(props: DashboardListProps) { const canCreate = hasPerm('can_write'); const canEdit = hasPerm('can_write'); const canDelete = hasPerm('can_write'); - const canExport = hasPerm('can_read'); + const canExport = hasPerm('can_export'); const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }]; diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx index a489240b0f..c5a0183fbd 100644 --- a/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx @@ -163,7 +163,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) { const canEdit = hasPerm('can_write'); const canDelete = hasPerm('can_write'); const canExport = - hasPerm('can_read') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); + hasPerm('can_export') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); const menuData: SubMenuProps = { activeChild: 'Databases', diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx index 2ef09249fe..ea3048cf57 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx @@ -152,7 +152,7 @@ const DatasetList: FunctionComponent = ({ const canEdit = hasPerm('can_write'); const canDelete = hasPerm('can_write'); const canCreate = hasPerm('can_write'); - const canExport = hasPerm('can_read'); + const canExport = hasPerm('can_export'); const initialSort = SORT_BY; diff --git a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.test.jsx b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.test.jsx index 8e1c60d669..d3512311c2 100644 --- a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.test.jsx +++ b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.test.jsx @@ -111,7 +111,7 @@ const mockImportFile = new File( ); fetchMock.get(queriesInfoEndpoint, { - permissions: ['can_write', 'can_read'], + permissions: ['can_write', 'can_read', 'can_export'], }); fetchMock.get(queriesEndpoint, { result: mockqueries, diff --git a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx index d6df355e02..f6d27a463e 100644 --- a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx +++ b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx @@ -133,7 +133,7 @@ function SavedQueryList({ const canEdit = hasPerm('can_write'); const canDelete = hasPerm('can_write'); const canExport = - hasPerm('can_read') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); + hasPerm('can_export') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); const openNewQuery = () => { window.open(`${window.location.origin}/superset/sqllab?new=true`); diff --git a/superset/constants.py b/superset/constants.py index 6ea189bd81..bffb02ceea 100644 --- a/superset/constants.py +++ b/superset/constants.py @@ -100,7 +100,6 @@ MODEL_API_RW_METHOD_PERMISSION_MAP = { "bulk_delete": "write", "delete": "write", "distinct": "read", - "export": "read", "get": "read", "get_list": "read", "info": "read", diff --git a/tests/integration_tests/charts/api_tests.py b/tests/integration_tests/charts/api_tests.py index e6d952939e..ba49c3e380 100644 --- a/tests/integration_tests/charts/api_tests.py +++ b/tests/integration_tests/charts/api_tests.py @@ -183,10 +183,7 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin): rv = self.get_assert_metric(uri, "info") data = json.loads(rv.data.decode("utf-8")) assert rv.status_code == 200 - assert set(data["permissions"]) == { - "can_read", - "can_write", - } + assert set(data["permissions"]) == {"can_read", "can_write", "can_export"} def create_chart_import(self): buf = BytesIO() diff --git a/tests/integration_tests/dashboards/api_tests.py b/tests/integration_tests/dashboards/api_tests.py index 87e475aa16..360a204c3e 100644 --- a/tests/integration_tests/dashboards/api_tests.py +++ b/tests/integration_tests/dashboards/api_tests.py @@ -378,9 +378,7 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi rv = self.get_assert_metric(uri, "info") data = json.loads(rv.data.decode("utf-8")) assert rv.status_code == 200 - assert "can_read" in data["permissions"] - assert "can_write" in data["permissions"] - assert len(data["permissions"]) == 2 + assert set(data["permissions"]) == {"can_read", "can_write", "can_export"} @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices") def test_get_dashboard_not_found(self): diff --git a/tests/integration_tests/databases/api_tests.py b/tests/integration_tests/databases/api_tests.py index e13559e181..9fc6fb99da 100644 --- a/tests/integration_tests/databases/api_tests.py +++ b/tests/integration_tests/databases/api_tests.py @@ -744,9 +744,7 @@ class TestDatabaseApi(SupersetTestCase): rv = self.get_assert_metric(uri, "info") data = json.loads(rv.data.decode("utf-8")) assert rv.status_code == 200 - assert "can_read" in data["permissions"] - assert "can_write" in data["permissions"] - assert len(data["permissions"]) == 2 + assert set(data["permissions"]) == {"can_read", "can_write", "can_export"} def test_get_invalid_database_table_metadata(self): """ @@ -1143,9 +1141,7 @@ class TestDatabaseApi(SupersetTestCase): argument = [database.id] uri = f"api/v1/database/export/?q={prison.dumps(argument)}" rv = self.client.get(uri) - # export only requires can_read now, but gamma need to have explicit access to - # view the database - assert rv.status_code == 404 + assert rv.status_code == 401 def test_export_database_non_existing(self): """ diff --git a/tests/integration_tests/datasets/api_tests.py b/tests/integration_tests/datasets/api_tests.py index 229fa21ae2..50b01e3c7a 100644 --- a/tests/integration_tests/datasets/api_tests.py +++ b/tests/integration_tests/datasets/api_tests.py @@ -375,9 +375,7 @@ class TestDatasetApi(SupersetTestCase): rv = self.get_assert_metric(uri, "info") data = json.loads(rv.data.decode("utf-8")) assert rv.status_code == 200 - assert "can_read" in data["permissions"] - assert "can_write" in data["permissions"] - assert len(data["permissions"]) == 2 + assert set(data["permissions"]) == {"can_read", "can_write", "can_export"} def test_create_dataset_item(self): """ @@ -1382,18 +1380,33 @@ class TestDatasetApi(SupersetTestCase): rv = self.get_assert_metric(uri, "export") assert rv.status_code == 404 + @pytest.mark.usefixtures("create_datasets") def test_export_dataset_gamma(self): """ Dataset API: Test export dataset has gamma """ - birth_names_dataset = self.get_birth_names_dataset() + dataset = self.get_fixture_datasets()[0] - argument = [birth_names_dataset.id] + argument = [dataset.id] uri = f"api/v1/dataset/export/?q={prison.dumps(argument)}" self.login(username="gamma") rv = self.client.get(uri) - assert rv.status_code == 404 + assert rv.status_code == 401 + + perm1 = security_manager.find_permission_view_menu("can_export", "Dataset") + + perm2 = security_manager.find_permission_view_menu( + "datasource_access", dataset.perm + ) + + # add perissions to allow export + access to query this dataset + gamma_role = security_manager.find_role("Gamma") + security_manager.add_permission_role(gamma_role, perm1) + security_manager.add_permission_role(gamma_role, perm2) + + rv = self.client.get(uri) + assert rv.status_code == 200 @patch.dict( "superset.extensions.feature_flag_manager._feature_flags", @@ -1444,19 +1457,20 @@ class TestDatasetApi(SupersetTestCase): {"VERSIONED_EXPORT": True}, clear=True, ) + @pytest.mark.usefixtures("create_datasets") def test_export_dataset_bundle_gamma(self): """ Dataset API: Test export dataset has gamma """ - birth_names_dataset = self.get_birth_names_dataset() + dataset = self.get_fixture_datasets()[0] - argument = [birth_names_dataset.id] + argument = [dataset.id] uri = f"api/v1/dataset/export/?q={prison.dumps(argument)}" self.login(username="gamma") rv = self.client.get(uri) # gamma users by default do not have access to this dataset - assert rv.status_code == 404 + assert rv.status_code == 401 @unittest.skip("Number of related objects depend on DB") @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") diff --git a/tests/integration_tests/queries/saved_queries/api_tests.py b/tests/integration_tests/queries/saved_queries/api_tests.py index 7cb150894b..cf698d370a 100644 --- a/tests/integration_tests/queries/saved_queries/api_tests.py +++ b/tests/integration_tests/queries/saved_queries/api_tests.py @@ -434,9 +434,7 @@ class TestSavedQueryApi(SupersetTestCase): rv = self.get_assert_metric(uri, "info") data = json.loads(rv.data.decode("utf-8")) assert rv.status_code == 200 - assert "can_read" in data["permissions"] - assert "can_write" in data["permissions"] - assert len(data["permissions"]) == 2 + assert set(data["permissions"]) == {"can_read", "can_write", "can_export"} def test_related_saved_query(self): """