You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2023/08/07 19:20:27 UTC

[superset] branch 3.0 updated (e9e06a522f -> 8335afaa52)

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

michaelsmolina pushed a change to branch 3.0
in repository https://gitbox.apache.org/repos/asf/superset.git


 discard e9e06a522f fix: Pylint errors from cherry-picking
     new 7fe61ccf7d fix: Pylint errors from cherry-picking
     new 215b3b5a4b fix: revert "fix(embedded): adding logic to check dataset used by filters (#24808) (#24892)
     new c8c7539ff1 fix: Dashboard aware RBAC dataset permission (#24789)
     new 8335afaa52 fix: Migration to fix out of sync schema_perm in charts and datasets (#24884)

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (e9e06a522f)
            \
             N -- N -- N   refs/heads/3.0 (8335afaa52)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 ...23_0769ef90fddd_fix_schema_perm_for_datasets.py | 108 +++++++++++++++++++++
 superset/security/manager.py                       |  64 ++----------
 superset/views/core.py                             |   4 +-
 .../dashboards/security/security_rbac_tests.py     |   3 +-
 ...ef90fddd_fix_schema_perm_for_datasets__tests.py |  56 +++++++++++
 .../security/guest_token_security_tests.py         |  81 ----------------
 tests/integration_tests/security_tests.py          |  47 ++++++++-
 7 files changed, 221 insertions(+), 142 deletions(-)
 create mode 100644 superset/migrations/versions/2023-08-02_15-23_0769ef90fddd_fix_schema_perm_for_datasets.py
 create mode 100644 tests/integration_tests/migrations/0769ef90fddd_fix_schema_perm_for_datasets__tests.py


[superset] 02/04: fix: revert "fix(embedded): adding logic to check dataset used by filters (#24808) (#24892)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 215b3b5a4b43b978c2f47313846b520c092791fe
Author: John Bodley <45...@users.noreply.github.com>
AuthorDate: Fri Aug 4 11:14:57 2023 -0700

    fix: revert "fix(embedded): adding logic to check dataset used by filters (#24808) (#24892)
    
    (cherry picked from commit 9f7f2c60d61c5a76983d01e0dd15483366952197)
---
 superset/security/manager.py                       | 25 +-----------
 .../security/guest_token_security_tests.py         | 46 ----------------------
 2 files changed, 2 insertions(+), 69 deletions(-)

diff --git a/superset/security/manager.py b/superset/security/manager.py
index b4b6643976..28354ac18d 100644
--- a/superset/security/manager.py
+++ b/superset/security/manager.py
@@ -16,7 +16,6 @@
 # under the License.
 # pylint: disable=too-many-lines
 """A set of constants and methods to manage permissions and security"""
-import json
 import logging
 import re
 import time
@@ -2063,28 +2062,8 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
             .filter(Dashboard.id.in_(dashboard_ids))
         )
 
-        if db.session.query(query.exists()).scalar():
-            return True
-
-        # check for datasets that are only used by filters
-        dashboards_json = (
-            db.session.query(Dashboard.json_metadata)
-            .filter(Dashboard.id.in_(dashboard_ids))
-            .all()
-        )
-        for json_ in dashboards_json:
-            try:
-                json_metadata = json.loads(json_.json_metadata)
-                for filter_ in json_metadata.get("native_filter_configuration", []):
-                    filter_dataset_ids = [
-                        target.get("datasetId") for target in filter_.get("targets", [])
-                    ]
-                    if datasource.id in filter_dataset_ids:
-                        return True
-            except ValueError:
-                pass
-
-        return False
+        exists = db.session.query(query.exists()).scalar()
+        return exists
 
     @staticmethod
     def _get_current_epoch_time() -> float:
diff --git a/tests/integration_tests/security/guest_token_security_tests.py b/tests/integration_tests/security/guest_token_security_tests.py
index e5bf589184..e0517c9b28 100644
--- a/tests/integration_tests/security/guest_token_security_tests.py
+++ b/tests/integration_tests/security/guest_token_security_tests.py
@@ -15,21 +15,18 @@
 # specific language governing permissions and limitations
 # under the License.
 """Unit tests for Superset"""
-import json
 from unittest import mock
 
 import pytest
 from flask import g
 
 from superset import db, security_manager
-from superset.connectors.sqla.models import SqlaTable
 from superset.daos.dashboard import EmbeddedDashboardDAO
 from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 from superset.exceptions import SupersetSecurityException
 from superset.models.dashboard import Dashboard
 from superset.security.guest_token import GuestTokenResourceType
 from superset.sql_parse import Table
-from superset.utils.database import get_example_database
 from tests.integration_tests.base_tests import SupersetTestCase
 from tests.integration_tests.fixtures.birth_names_dashboard import (
     load_birth_names_dashboard_with_slices,
@@ -236,46 +233,3 @@ class TestGuestUserDashboardAccess(SupersetTestCase):
 
         db.session.delete(dash)
         db.session.commit()
-
-    def test_can_access_datasource_used_in_dashboard_filter(self):
-        """
-        Test that a user can access a datasource used only by a filter in a dashboard
-        they have access to.
-        """
-        # Create a test dataset
-        test_dataset = SqlaTable(
-            database_id=get_example_database().id,
-            schema="main",
-            table_name="test_table_embedded_filter",
-        )
-        db.session.add(test_dataset)
-        db.session.commit()
-
-        # Create an embedabble dashboard with a filter powered by the test dataset
-        test_dashboard = Dashboard()
-        test_dashboard.dashboard_title = "Test Embedded Dashboard"
-        test_dashboard.json_metadata = json.dumps(
-            {
-                "native_filter_configuration": [
-                    {"targets": [{"datasetId": test_dataset.id}]}
-                ]
-            }
-        )
-        test_dashboard.owners = []
-        test_dashboard.slices = []
-        test_dashboard.published = False
-        db.session.add(test_dashboard)
-        db.session.commit()
-        self.embedded = EmbeddedDashboardDAO.upsert(test_dashboard, [])
-
-        # grant access to the dashboad
-        g.user = self.authorized_guest
-        g.user.resources = [{"type": "dashboard", "id": str(self.embedded.uuid)}]
-        g.user.roles = [security_manager.get_public_role()]
-
-        # The user should have access to the datasource via the dashboard
-        security_manager.raise_for_access(datasource=test_dataset)
-
-        db.session.delete(test_dashboard)
-        db.session.delete(test_dataset)
-        db.session.commit()


[superset] 01/04: fix: Pylint errors from cherry-picking

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 7fe61ccf7d7cae28d3215fd0082d3d494dd0ba7a
Author: Michael S. Molina <mi...@gmail.com>
AuthorDate: Fri Aug 4 15:05:51 2023 -0300

    fix: Pylint errors from cherry-picking
---
 superset/views/api.py  | 2 +-
 superset/views/core.py | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/superset/views/api.py b/superset/views/api.py
index 267d41dd75..c9473da1b3 100644
--- a/superset/views/api.py
+++ b/superset/views/api.py
@@ -73,7 +73,7 @@ class Api(BaseSupersetView):
     @handle_api_exception
     @has_access_api
     @expose("/v1/form_data/", methods=("GET",))
-    def query_form_data(self) -> FlaskResponse:  # pylint: disable=no-self-use
+    def query_form_data(self) -> FlaskResponse:
         """
         Get the formdata stored in the database for existing slice.
         params: slice_id: integer
diff --git a/superset/views/core.py b/superset/views/core.py
index cf1d8db08c..4508ec70c0 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -941,7 +941,8 @@ class Superset(BaseSupersetView):  # pylint: disable=too-many-public-methods
     @deprecated(
         new_target="api/v1/database/<int:pk>/table/<path:table_name>/<schema_name>/"
     )
-    def fetch_datasource_metadata(self) -> FlaskResponse:  # pylint: disable=no-self-use
+    # pylint: disable=no-self-use
+    def fetch_datasource_metadata(self) -> FlaskResponse:
         """
         Fetch the datasource metadata.
 


[superset] 03/04: fix: Dashboard aware RBAC dataset permission (#24789)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/superset.git

commit c8c7539ff12b994e55ec90d0104bef53f5669a10
Author: John Bodley <45...@users.noreply.github.com>
AuthorDate: Fri Aug 4 11:53:34 2023 -0700

    fix: Dashboard aware RBAC dataset permission (#24789)
    
    (cherry picked from commit 7397ab36f2872a709a5219e5318bd79aacb89930)
---
 superset/security/manager.py                       | 43 +++++---------------
 .../dashboards/security/security_rbac_tests.py     |  3 +-
 .../security/guest_token_security_tests.py         | 35 ----------------
 tests/integration_tests/security_tests.py          | 47 +++++++++++++++++++++-
 4 files changed, 55 insertions(+), 73 deletions(-)

diff --git a/superset/security/manager.py b/superset/security/manager.py
index 28354ac18d..f57e730af0 100644
--- a/superset/security/manager.py
+++ b/superset/security/manager.py
@@ -24,7 +24,6 @@ from typing import Any, Callable, cast, NamedTuple, Optional, TYPE_CHECKING, Uni
 
 from flask import current_app, Flask, g, Request
 from flask_appbuilder import Model
-from flask_appbuilder.models.sqla.interface import SQLAInterface
 from flask_appbuilder.security.sqla.manager import SecurityManager
 from flask_appbuilder.security.sqla.models import (
     assoc_permissionview_role,
@@ -1785,7 +1784,7 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
 
         # pylint: disable=import-outside-toplevel
         from superset.connectors.sqla.models import SqlaTable
-        from superset.extensions import feature_flag_manager
+        from superset.daos.dashboard import DashboardDAO
         from superset.sql_parse import Table
 
         if database and table or query:
@@ -1831,25 +1830,26 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
                 )
 
         if datasource or query_context or viz:
+            form_data = None
+
             if query_context:
                 datasource = query_context.datasource
+                form_data = query_context.form_data
             elif viz:
                 datasource = viz.datasource
+                form_data = viz.form_data
 
             assert datasource
 
-            should_check_dashboard_access = (
-                feature_flag_manager.is_feature_enabled("DASHBOARD_RBAC")
-                or self.is_guest_user()
-            )
-
             if not (
                 self.can_access_schema(datasource)
                 or self.can_access("datasource_access", datasource.perm or "")
                 or self.is_owner(datasource)
                 or (
-                    should_check_dashboard_access
-                    and self.can_access_based_on_dashboard(datasource)
+                    form_data
+                    and (dashboard_id := form_data.get("dashboardId"))
+                    and (dashboard := DashboardDAO.find_by_id(dashboard_id))
+                    and self.can_access_dashboard(dashboard)
                 )
             ):
                 raise SupersetSecurityException(
@@ -2040,31 +2040,6 @@ class SupersetSecurityManager(  # pylint: disable=too-many-public-methods
 
         raise DashboardAccessDeniedError()
 
-    @staticmethod
-    def can_access_based_on_dashboard(datasource: "BaseDatasource") -> bool:
-        # pylint: disable=import-outside-toplevel
-        from superset import db
-        from superset.dashboards.filters import DashboardAccessFilter
-        from superset.models.dashboard import Dashboard
-        from superset.models.slice import Slice
-
-        dashboard_ids = db.session.query(Dashboard.id)
-        dashboard_ids = DashboardAccessFilter(
-            "id", SQLAInterface(Dashboard, db.session)
-        ).apply(dashboard_ids, None)
-
-        datasource_class = type(datasource)
-        query = (
-            db.session.query(Dashboard.id)
-            .join(Slice, Dashboard.slices)
-            .join(Slice.table)
-            .filter(datasource_class.id == datasource.id)
-            .filter(Dashboard.id.in_(dashboard_ids))
-        )
-
-        exists = db.session.query(query.exists()).scalar()
-        return exists
-
     @staticmethod
     def _get_current_epoch_time() -> float:
         """This is used so the tests can mock time"""
diff --git a/tests/integration_tests/dashboards/security/security_rbac_tests.py b/tests/integration_tests/dashboards/security/security_rbac_tests.py
index 03b5da7ce3..ba464064c3 100644
--- a/tests/integration_tests/dashboards/security/security_rbac_tests.py
+++ b/tests/integration_tests/dashboards/security/security_rbac_tests.py
@@ -169,7 +169,6 @@ class TestDashboardRoleBasedSecurity(BaseTestDashboardSecurity):
             return
 
         # arrange
-
         username = random_str()
         new_role = f"role_{random_str()}"
         self.create_user_with_roles(username, [new_role], should_create_roles=True)
@@ -191,7 +190,7 @@ class TestDashboardRoleBasedSecurity(BaseTestDashboardSecurity):
 
         request_payload = get_query_context("birth_names")
         rv = self.post_assert_metric(CHART_DATA_URI, request_payload, "data")
-        self.assertEqual(rv.status_code, 200)
+        self.assertEqual(rv.status_code, 403)
 
         # post
         revoke_access_to_dashboard(dashboard_to_access, new_role)
diff --git a/tests/integration_tests/security/guest_token_security_tests.py b/tests/integration_tests/security/guest_token_security_tests.py
index e0517c9b28..73dcb1b932 100644
--- a/tests/integration_tests/security/guest_token_security_tests.py
+++ b/tests/integration_tests/security/guest_token_security_tests.py
@@ -159,41 +159,6 @@ class TestGuestUserDashboardAccess(SupersetTestCase):
         has_guest_access = security_manager.has_guest_access(self.dash)
         self.assertFalse(has_guest_access)
 
-    def test_chart_raise_for_access_as_guest(self):
-        chart = self.dash.slices[0]
-        g.user = self.authorized_guest
-
-        security_manager.raise_for_access(viz=chart)
-
-    def test_chart_raise_for_access_as_unauthorized_guest(self):
-        chart = self.dash.slices[0]
-        g.user = self.unauthorized_guest
-
-        with self.assertRaises(SupersetSecurityException):
-            security_manager.raise_for_access(viz=chart)
-
-    def test_dataset_raise_for_access_as_guest(self):
-        dataset = self.dash.slices[0].datasource
-        g.user = self.authorized_guest
-
-        security_manager.raise_for_access(datasource=dataset)
-
-    def test_dataset_raise_for_access_as_unauthorized_guest(self):
-        dataset = self.dash.slices[0].datasource
-        g.user = self.unauthorized_guest
-
-        with self.assertRaises(SupersetSecurityException):
-            security_manager.raise_for_access(datasource=dataset)
-
-    def test_guest_token_does_not_grant_access_to_underlying_table(self):
-        sqla_table = self.dash.slices[0].table
-        table = Table(table=sqla_table.table_name)
-
-        g.user = self.authorized_guest
-
-        with self.assertRaises(Exception):
-            security_manager.raise_for_access(table=table, database=sqla_table.database)
-
     def test_raise_for_dashboard_access_as_guest(self):
         g.user = self.authorized_guest
 
diff --git a/tests/integration_tests/security_tests.py b/tests/integration_tests/security_tests.py
index 1518a69f9d..139ad26342 100644
--- a/tests/integration_tests/security_tests.py
+++ b/tests/integration_tests/security_tests.py
@@ -47,6 +47,7 @@ from superset.utils.database import get_example_database
 from superset.utils.urls import get_url_host
 
 from .base_tests import SupersetTestCase
+from tests.integration_tests.conftest import with_feature_flags
 from tests.integration_tests.fixtures.public_role import (
     public_role_like_gamma,
     public_role_like_test_role,
@@ -1643,17 +1644,19 @@ class TestSecurityManager(SupersetTestCase):
         with self.assertRaises(SupersetSecurityException):
             security_manager.raise_for_access(query=query)
 
+    @patch("superset.security.manager.g")
     @patch("superset.security.SupersetSecurityManager.is_owner")
     @patch("superset.security.SupersetSecurityManager.can_access")
     @patch("superset.security.SupersetSecurityManager.can_access_schema")
     def test_raise_for_access_query_context(
-        self, mock_can_access_schema, mock_can_access, mock_is_owner
+        self, mock_can_access_schema, mock_can_access, mock_is_owner, mock_g
     ):
         query_context = Mock(datasource=self.get_datasource_mock())
 
         mock_can_access_schema.return_value = True
         security_manager.raise_for_access(query_context=query_context)
 
+        mock_g.user = security_manager.find_user("gamma")
         mock_can_access.return_value = False
         mock_can_access_schema.return_value = False
         mock_is_owner.return_value = False
@@ -1674,17 +1677,19 @@ class TestSecurityManager(SupersetTestCase):
         with self.assertRaises(SupersetSecurityException):
             security_manager.raise_for_access(database=database, table=table)
 
+    @patch("superset.security.manager.g")
     @patch("superset.security.SupersetSecurityManager.is_owner")
     @patch("superset.security.SupersetSecurityManager.can_access")
     @patch("superset.security.SupersetSecurityManager.can_access_schema")
     def test_raise_for_access_viz(
-        self, mock_can_access_schema, mock_can_access, mock_is_owner
+        self, mock_can_access_schema, mock_can_access, mock_is_owner, mock_g
     ):
         test_viz = viz.TimeTableViz(self.get_datasource_mock(), form_data={})
 
         mock_can_access_schema.return_value = True
         security_manager.raise_for_access(viz=test_viz)
 
+        mock_g.user = security_manager.find_user("gamma")
         mock_can_access.return_value = False
         mock_can_access_schema.return_value = False
         mock_is_owner.return_value = False
@@ -1692,6 +1697,44 @@ class TestSecurityManager(SupersetTestCase):
         with self.assertRaises(SupersetSecurityException):
             security_manager.raise_for_access(viz=test_viz)
 
+    @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
+    @with_feature_flags(DASHBOARD_RBAC=True)
+    @patch("superset.security.manager.g")
+    @patch("superset.security.SupersetSecurityManager.is_owner")
+    @patch("superset.security.SupersetSecurityManager.can_access")
+    @patch("superset.security.SupersetSecurityManager.can_access_schema")
+    def test_raise_for_access_rbac(
+        self,
+        mock_can_access_schema,
+        mock_can_access,
+        mock_is_owner,
+        mock_g,
+    ):
+        dashboard = self.get_dash_by_slug("births")
+
+        obj = Mock(
+            datasource=self.get_datasource_mock(),
+            form_data={"dashboardId": dashboard.id},
+        )
+
+        mock_g.user = security_manager.find_user("gamma")
+        mock_is_owner.return_value = False
+        mock_can_access.return_value = False
+        mock_can_access_schema.return_value = False
+
+        for kwarg in ["query_context", "viz"]:
+            dashboard.roles = []
+            db.session.flush()
+
+            with self.assertRaises(SupersetSecurityException):
+                security_manager.raise_for_access(**{kwarg: obj})
+
+            dashboard.roles = [self.get_role("Gamma")]
+            db.session.flush()
+            security_manager.raise_for_access(**{kwarg: obj})
+
+        db.session.rollback()
+
     @patch("superset.security.manager.g")
     def test_get_user_roles(self, mock_g):
         admin = security_manager.find_user("admin")


[superset] 04/04: fix: Migration to fix out of sync schema_perm in charts and datasets (#24884)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 8335afaa5230f43135c784fcc7997a166d19a306
Author: Jack Fragassi <jf...@gmail.com>
AuthorDate: Mon Aug 7 09:34:47 2023 -0700

    fix: Migration to fix out of sync schema_perm in charts and datasets (#24884)
    
    (cherry picked from commit 07992c11e73acbf4debf55555b8cdc8a96e50d0e)
---
 ...23_0769ef90fddd_fix_schema_perm_for_datasets.py | 108 +++++++++++++++++++++
 ...ef90fddd_fix_schema_perm_for_datasets__tests.py |  56 +++++++++++
 2 files changed, 164 insertions(+)

diff --git a/superset/migrations/versions/2023-08-02_15-23_0769ef90fddd_fix_schema_perm_for_datasets.py b/superset/migrations/versions/2023-08-02_15-23_0769ef90fddd_fix_schema_perm_for_datasets.py
new file mode 100644
index 0000000000..f96dcd26d5
--- /dev/null
+++ b/superset/migrations/versions/2023-08-02_15-23_0769ef90fddd_fix_schema_perm_for_datasets.py
@@ -0,0 +1,108 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Fix schema perm for datasets
+
+Revision ID: 0769ef90fddd
+Revises: ee179a490af9
+Create Date: 2023-08-02 15:23:58.242396
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = "0769ef90fddd"
+down_revision = "ee179a490af9"
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.dialects.sqlite.base import SQLiteDialect
+from sqlalchemy.ext.declarative import declarative_base
+
+from superset import db
+
+Base = declarative_base()
+
+
+class SqlaTable(Base):
+    __tablename__ = "tables"
+
+    id = sa.Column(sa.Integer, primary_key=True)
+    schema = sa.Column(sa.String(255))
+    schema_perm = sa.Column(sa.String(1000))
+    database_id = sa.Column(sa.Integer, sa.ForeignKey("dbs.id"))
+
+
+class Slice(Base):
+    __tablename__ = "slices"
+
+    id = sa.Column(sa.Integer, primary_key=True)
+    schema_perm = sa.Column(sa.String(1000))
+    datasource_id = sa.Column(sa.Integer)
+
+
+class Database(Base):
+    __tablename__ = "dbs"
+
+    id = sa.Column(sa.Integer, primary_key=True)
+    database_name = sa.Column(sa.String(250))
+
+
+def fix_datasets_schema_perm(session):
+    for result in (
+        session.query(SqlaTable, Database.database_name)
+        .join(Database)
+        .filter(SqlaTable.schema.isnot(None))
+        .filter(
+            SqlaTable.schema_perm
+            != sa.func.concat("[", Database.database_name, "].[", SqlaTable.schema, "]")
+        )
+    ):
+        result.SqlaTable.schema_perm = (
+            f"[{result.database_name}].[{result.SqlaTable.schema}]"
+        )
+
+
+def fix_charts_schema_perm(session):
+    for result in (
+        session.query(Slice, SqlaTable, Database.database_name)
+        .join(SqlaTable, Slice.datasource_id == SqlaTable.id)
+        .join(Database, SqlaTable.database_id == Database.id)
+        .filter(SqlaTable.schema.isnot(None))
+        .filter(
+            Slice.schema_perm
+            != sa.func.concat("[", Database.database_name, "].[", SqlaTable.schema, "]")
+        )
+    ):
+        result.Slice.schema_perm = (
+            f"[{result.database_name}].[{result.SqlaTable.schema}]"
+        )
+
+
+def upgrade():
+    bind = op.get_bind()
+    session = db.Session(bind=bind)
+    if isinstance(bind.dialect, SQLiteDialect):
+        return  # sqlite doesn't have a concat function
+
+    fix_datasets_schema_perm(session)
+    fix_charts_schema_perm(session)
+
+    session.commit()
+    session.close()
+
+
+def downgrade():
+    pass
diff --git a/tests/integration_tests/migrations/0769ef90fddd_fix_schema_perm_for_datasets__tests.py b/tests/integration_tests/migrations/0769ef90fddd_fix_schema_perm_for_datasets__tests.py
new file mode 100644
index 0000000000..1b74a7f048
--- /dev/null
+++ b/tests/integration_tests/migrations/0769ef90fddd_fix_schema_perm_for_datasets__tests.py
@@ -0,0 +1,56 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from importlib import import_module
+
+import pytest
+
+from superset import db
+from superset.connectors.sqla.models import SqlaTable
+from superset.models.slice import Slice
+from superset.utils.core import backend, get_example_default_schema
+from tests.integration_tests.fixtures.birth_names_dashboard import (
+    load_birth_names_dashboard_with_slices,
+    load_birth_names_data,
+)
+
+migration_module = import_module(
+    "superset.migrations.versions."
+    "2023-08-02_15-23_0769ef90fddd_fix_schema_perm_for_datasets"
+)
+
+fix_datasets_schema_perm = migration_module.fix_datasets_schema_perm
+fix_charts_schema_perm = migration_module.fix_charts_schema_perm
+
+
+@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
+def test_fix_schema_perm():
+    if backend() == "sqlite":
+        return
+
+    dataset = db.session.query(SqlaTable).filter_by(table_name="birth_names").one()
+    chart = db.session.query(Slice).filter_by(slice_name="Girls").one()
+    dataset.schema_perm = "wrong"
+    chart.schema_perm = "wrong"
+    db.session.commit()
+
+    fix_datasets_schema_perm(db.session)
+    db.session.commit()
+    assert dataset.schema_perm == f"[examples].[{get_example_default_schema()}]"
+
+    fix_charts_schema_perm(db.session)
+    db.session.commit()
+    assert chart.schema_perm == f"[examples].[{get_example_default_schema()}]"