You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by "john-bodley (via GitHub)" <gi...@apache.org> on 2023/06/10 02:55:38 UTC

[GitHub] [superset] john-bodley opened a new pull request, #24350: fix: Address dashboard permission regression in #23586

john-bodley opened a new pull request, #24350:
URL: https://github.com/apache/superset/pull/24350

   <!---
   Please write the PR title following the conventions at https://www.conventionalcommits.org/en/v1.0.0/
   Example:
   fix(dashboard): load charts correctly
   -->
   
   ### SUMMARY
   <!--- Describe the change below, including rationale and design decisions -->
   
   ### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
   <!--- Skip this if not applicable -->
   
   ### TESTING INSTRUCTIONS
   <!--- Required! What steps can be taken to manually verify the changes? -->
   
   ### ADDITIONAL INFORMATION
   <!--- Check any relevant boxes with "x" -->
   <!--- HINT: Include "Fixes #nnn" if you are fixing an existing issue -->
   - [ ] Has associated issue:
   - [ ] Required feature flags:
   - [ ] Changes UI
   - [ ] Includes DB Migration (follow approval process in [SIP-59](https://github.com/apache/superset/issues/13351))
     - [ ] Migration is atomic, supports rollback & is backwards-compatible
     - [ ] Confirm DB migration upgrade and downgrade tested
     - [ ] Runtime estimates and downtime expectations provided
   - [ ] Introduces new feature or API
   - [ ] Removes existing feature or API
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225587171


##########
superset/views/core.py:
##########
@@ -1641,76 +1639,57 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
+
+        dashboard = Dashboard.get(dashboard_id_or_slug)

Review Comment:
   This is concerning that this is different than the [DashboardDAO.get_by_id_or_slug](https://github.com/apache/superset/blob/0e3f1f638c06de11668c37329ca7141fd1159293/superset/dashboards/dao.py#L45-L68) and that the DAO logic differs (from a filter perspective) whether you pass in a UUID versus a slug/ID.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225601236


##########
tests/integration_tests/dashboards/security/security_dataset_tests.py:
##########
@@ -234,3 +235,4 @@ def test_get_dashboards_api_no_data_access(self):
         self.assert200(rv)
         data = json.loads(rv.data.decode("utf-8"))
         self.assertEqual(0, data["count"])
+        DashboardDAO.delete(dashboard)

Review Comment:
   Previously this was non-idempotent.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225589182


##########
superset/views/core.py:
##########
@@ -1641,76 +1639,57 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
+
+        dashboard = Dashboard.get(dashboard_id_or_slug)
+
         if not dashboard:
             abort(404)
 
-        assert dashboard is not None
-
-        has_access_ = False
-        for datasource in dashboard.datasources:
-            datasource = DatasourceDAO.get_datasource(
-                datasource_type=DatasourceType(datasource.type),
-                datasource_id=datasource.id,
-                session=db.session(),
+        try:
+            security_manager.raise_for_dashboard_access(dashboard)
+        except DashboardAccessDeniedError as ex:
+            return redirect_with_flash(
+                url="/dashboard/list/",
+                message=utils.error_msg_from_exception(ex),
+                category="danger",
             )
-            if datasource and security_manager.can_access_datasource(
-                datasource=datasource,
-            ):
-                has_access_ = True
-
-            if has_access_:
-                break
-
-        if dashboard.datasources and not has_access_:
-            flash(DashboardAccessDeniedError.message, "danger")
-            return redirect(DASHBOARD_LIST_URL)
-
-        dash_edit_perm = security_manager.is_owner(

Review Comment:
   This is inlined later for improved readability.



##########
superset/views/core.py:
##########
@@ -1641,76 +1648,54 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
-        if not dashboard:
-            abort(404)
 
-        assert dashboard is not None
-
-        has_access_ = False
-        for datasource in dashboard.datasources:
-            datasource = DatasourceDAO.get_datasource(
-                datasource_type=DatasourceType(datasource.type),
-                datasource_id=datasource.id,
-                session=db.session(),
+        try:
+           dashboard = DashboardDAO.get_by_id_or_slug(dashboard_id_or_slug)
+        except DashboardNotFoundError:
+            abort(404)
+        except DashboardAccessDeniedError as ex:
+            return redirect_with_flash(
+                url="/dashboard/list/",
+                message=utils.error_msg_from_exception(ex),
+                category="danger",
             )
-            if datasource and security_manager.can_access_datasource(
-                datasource=datasource,
-            ):
-                has_access_ = True
-
-            if has_access_:
-                break
-
-        if dashboard.datasources and not has_access_:
-            flash(DashboardAccessDeniedError.message, "danger")
-            return redirect(DASHBOARD_LIST_URL)
-
-        dash_edit_perm = security_manager.is_owner(
-            dashboard
-        ) and security_manager.can_access("can_save_dash", "Superset")
-        edit_mode = (
-            request.args.get(utils.ReservedUrlParameters.EDIT_MODE.value) == "true"
-        )
-
-        standalone_mode = ReservedUrlParameters.is_standalone_mode()
 
         add_extra_log_payload(
             dashboard_id=dashboard.id,
             dashboard_version="v2",
-            dash_edit_perm=dash_edit_perm,
-            edit_mode=edit_mode,
+            dash_edit_perm=(

Review Comment:
   Inlined logic from above to improve readability.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley merged pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley merged PR #24350:
URL: https://github.com/apache/superset/pull/24350


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] codecov[bot] commented on pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "codecov[bot] (via GitHub)" <gi...@apache.org>.
codecov[bot] commented on PR #24350:
URL: https://github.com/apache/superset/pull/24350#issuecomment-1585495632

   ## [Codecov](https://app.codecov.io/gh/apache/superset/pull/24350?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) Report
   > Merging [#24350](https://app.codecov.io/gh/apache/superset/pull/24350?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) (e52da5f) into [master](https://app.codecov.io/gh/apache/superset/commit/0e3f1f638c06de11668c37329ca7141fd1159293?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) (0e3f1f6) will **decrease** coverage by `11.20%`.
   > The diff coverage is `8.33%`.
   
   > :exclamation: Current head e52da5f differs from pull request most recent head 72017e2. Consider uploading reports for the commit 72017e2 to get more accurate results
   
   ```diff
   @@             Coverage Diff             @@
   ##           master   #24350       +/-   ##
   ===========================================
   - Coverage   69.09%   57.89%   -11.20%     
   ===========================================
     Files        1903     1903               
     Lines       74608    74584       -24     
     Branches     8107     8107               
   ===========================================
   - Hits        51550    43181     -8369     
   - Misses      20947    29292     +8345     
     Partials     2111     2111               
   ```
   
   | Flag | Coverage Δ | |
   |---|---|---|
   | hive | `?` | |
   | mysql | `?` | |
   | postgres | `?` | |
   | presto | `53.72% <8.33%> (+<0.01%)` | :arrow_up: |
   | python | `60.31% <8.33%> (-23.08%)` | :arrow_down: |
   | sqlite | `?` | |
   | unit | `54.21% <8.33%> (+<0.01%)` | :arrow_up: |
   
   Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#carryforward-flags-in-the-pull-request-comment) to find out more.
   
   | [Impacted Files](https://app.codecov.io/gh/apache/superset/pull/24350?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Coverage Δ | |
   |---|---|---|
   | [superset/utils/decorators.py](https://app.codecov.io/gh/apache/superset/pull/24350?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQvdXRpbHMvZGVjb3JhdG9ycy5weQ==) | `94.33% <ø> (-0.11%)` | :arrow_down: |
   | [superset/security/manager.py](https://app.codecov.io/gh/apache/superset/pull/24350?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQvc2VjdXJpdHkvbWFuYWdlci5weQ==) | `61.35% <5.88%> (-34.58%)` | :arrow_down: |
   | [superset/views/core.py](https://app.codecov.io/gh/apache/superset/pull/24350?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQvdmlld3MvY29yZS5weQ==) | `38.60% <14.28%> (-37.65%)` | :arrow_down: |
   
   ... and [299 files with indirect coverage changes](https://app.codecov.io/gh/apache/superset/pull/24350/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
   
   :mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225589330


##########
superset/views/core.py:
##########
@@ -1641,76 +1639,57 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
+
+        dashboard = Dashboard.get(dashboard_id_or_slug)
+
         if not dashboard:
             abort(404)
 
-        assert dashboard is not None
-
-        has_access_ = False
-        for datasource in dashboard.datasources:
-            datasource = DatasourceDAO.get_datasource(
-                datasource_type=DatasourceType(datasource.type),
-                datasource_id=datasource.id,
-                session=db.session(),
+        try:
+            security_manager.raise_for_dashboard_access(dashboard)
+        except DashboardAccessDeniedError as ex:
+            return redirect_with_flash(
+                url="/dashboard/list/",
+                message=utils.error_msg_from_exception(ex),
+                category="danger",
             )
-            if datasource and security_manager.can_access_datasource(
-                datasource=datasource,
-            ):
-                has_access_ = True
-
-            if has_access_:
-                break
-
-        if dashboard.datasources and not has_access_:
-            flash(DashboardAccessDeniedError.message, "danger")
-            return redirect(DASHBOARD_LIST_URL)
-
-        dash_edit_perm = security_manager.is_owner(
-            dashboard
-        ) and security_manager.can_access("can_save_dash", "Superset")
-        edit_mode = (
-            request.args.get(utils.ReservedUrlParameters.EDIT_MODE.value) == "true"
-        )
-
-        standalone_mode = ReservedUrlParameters.is_standalone_mode()
 
         add_extra_log_payload(
             dashboard_id=dashboard.id,
             dashboard_version="v2",
-            dash_edit_perm=dash_edit_perm,
-            edit_mode=edit_mode,
+            dash_edit_perm=(
+                security_manager.is_owner(dashboard)
+                and security_manager.can_access("can_save_dash", "Superset")
+            ),
+            edit_mode=(
+                request.args.get(ReservedUrlParameters.EDIT_MODE.value) == "true"
+            ),
         )
 
-        bootstrap_data = {

Review Comment:
   This is inlined later.



##########
superset/views/core.py:
##########
@@ -1641,76 +1648,54 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
-        if not dashboard:
-            abort(404)
 
-        assert dashboard is not None
-
-        has_access_ = False
-        for datasource in dashboard.datasources:
-            datasource = DatasourceDAO.get_datasource(
-                datasource_type=DatasourceType(datasource.type),
-                datasource_id=datasource.id,
-                session=db.session(),
+        try:
+           dashboard = DashboardDAO.get_by_id_or_slug(dashboard_id_or_slug)
+        except DashboardNotFoundError:
+            abort(404)
+        except DashboardAccessDeniedError as ex:
+            return redirect_with_flash(
+                url="/dashboard/list/",
+                message=utils.error_msg_from_exception(ex),
+                category="danger",
             )
-            if datasource and security_manager.can_access_datasource(
-                datasource=datasource,
-            ):
-                has_access_ = True
-
-            if has_access_:
-                break
-
-        if dashboard.datasources and not has_access_:
-            flash(DashboardAccessDeniedError.message, "danger")
-            return redirect(DASHBOARD_LIST_URL)
-
-        dash_edit_perm = security_manager.is_owner(
-            dashboard
-        ) and security_manager.can_access("can_save_dash", "Superset")
-        edit_mode = (
-            request.args.get(utils.ReservedUrlParameters.EDIT_MODE.value) == "true"
-        )
-
-        standalone_mode = ReservedUrlParameters.is_standalone_mode()
 
         add_extra_log_payload(
             dashboard_id=dashboard.id,
             dashboard_version="v2",
-            dash_edit_perm=dash_edit_perm,
-            edit_mode=edit_mode,
+            dash_edit_perm=(
+                security_manager.is_owner(dashboard)
+                and security_manager.can_access("can_save_dash", "Superset")
+            ),
+            edit_mode=(
+                request.args.get(ReservedUrlParameters.EDIT_MODE.value) == "true"
+            ),
         )
 
-        bootstrap_data = {
-            "user": bootstrap_user_data(g.user, include_perms=True),
-            "common": common_bootstrap_payload(g.user),
-        }
-
         return self.render_template(
             "superset/spa.html",
             entry="spa",
-            # dashboard title is always visible
-            title=dashboard.dashboard_title,
+            title=dashboard.dashboard_title,  # dashboard title is always visible
             bootstrap_data=json.dumps(
-                bootstrap_data, default=utils.pessimistic_json_iso_dttm_ser
+                {

Review Comment:
   Inlined logic from above.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225587171


##########
superset/views/core.py:
##########
@@ -1641,76 +1639,57 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
+
+        dashboard = Dashboard.get(dashboard_id_or_slug)

Review Comment:
   This is concerning that this is different than the [DashboardDAO.get_by_id_or_slug](https://github.com/apache/superset/blob/0e3f1f638c06de11668c37329ca7141fd1159293/superset/dashboards/dao.py#L45-L68) and that the DAO logic differs (from a filter perspective) whether you pass in a UUID versus a slug/ID.
   
   Personally the UUID logic is just adding more complexity to an already partially intractable problem in terms of understanding (and thus enforcing) the security model. Adhering to the [KISS principle](https://en.wikipedia.org/wiki/KISS_principle) in terms of the security model will actually make the service more  secure as it'll be easier to grok and enforce.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] michael-s-molina commented on pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "michael-s-molina (via GitHub)" <gi...@apache.org>.
michael-s-molina commented on PR #24350:
URL: https://github.com/apache/superset/pull/24350#issuecomment-1587227579

   > it may be a good idea to consider improving/clarifying this functionality when we start considering breaking changes for 4.0
   
   @villebro If you already have something in mind, you can add cards to the [Punt to 4.0](https://github.com/orgs/apache/projects/201) column in our board.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225587171


##########
superset/views/core.py:
##########
@@ -1641,76 +1639,57 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
+
+        dashboard = Dashboard.get(dashboard_id_or_slug)

Review Comment:
   This is concerning that this is different than the [DashboardDAO.get_by_id_or_slug](https://github.com/apache/superset/blob/0e3f1f638c06de11668c37329ca7141fd1159293/superset/dashboards/dao.py#L45-L68) and that the DAO logic differs whether you pass in a UUID versus a slug/ID, i.e., it seems like a security loophole.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225587171


##########
superset/views/core.py:
##########
@@ -1641,76 +1639,57 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
+
+        dashboard = Dashboard.get(dashboard_id_or_slug)

Review Comment:
   This is concerning that this is different than the DAO [here](https://github.com/apache/superset/blob/0e3f1f638c06de11668c37329ca7141fd1159293/superset/dashboards/dao.py#L45-L68) and that the DAO logic differs whether you pass in a UUID versus a slug/ID, i.e., it seems like a security loophole.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1227064349


##########
superset/security/manager.py:
##########
@@ -1995,38 +2010,40 @@ def raise_for_user_activity_access(user_id: int) -> None:
     def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
         """
         Raise an exception if the user cannot access the dashboard.
-        This does not check for the required role/permission pairs,
-        it only concerns itself with entity relationships.
+
+        This does not check for the required role/permission pairs, it only concerns
+        itself with entity relationships.
 
         :param dashboard: Dashboard the user wants access to
         :raises DashboardAccessDeniedError: If the user cannot access the resource
         """
+
         # pylint: disable=import-outside-toplevel
         from superset import is_feature_enabled
         from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 
-        def has_rbac_access() -> bool:
-            if not is_feature_enabled("DASHBOARD_RBAC") or not dashboard.roles:
-                return True
-
-            return any(
-                dashboard_role.id
-                in [user_role.id for user_role in self.get_user_roles()]
-                for dashboard_role in dashboard.roles
-            )
-
         if self.is_guest_user() and dashboard.embedded:
-            can_access = self.has_guest_access(dashboard)
+            if self.has_guest_access(dashboard):
+                return
         else:
-            can_access = (
-                self.is_admin()
-                or self.is_owner(dashboard)
-                or (dashboard.published and has_rbac_access())

Review Comment:
   @villebro is the desired end state to support only one or do you perceive both coexisting for some time?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225026280


##########
superset/views/core.py:
##########
@@ -1641,76 +1648,54 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
-        if not dashboard:
-            abort(404)
 
-        assert dashboard is not None
-
-        has_access_ = False
-        for datasource in dashboard.datasources:
-            datasource = DatasourceDAO.get_datasource(
-                datasource_type=DatasourceType(datasource.type),
-                datasource_id=datasource.id,
-                session=db.session(),
+        try:
+           dashboard = DashboardDAO.get_by_id_or_slug(dashboard_id_or_slug)
+        except DashboardNotFoundError:
+            abort(404)
+        except DashboardAccessDeniedError as ex:
+            return redirect_with_flash(
+                url="/dashboard/list/",
+                message=utils.error_msg_from_exception(ex),
+                category="danger",
             )
-            if datasource and security_manager.can_access_datasource(
-                datasource=datasource,
-            ):
-                has_access_ = True
-
-            if has_access_:
-                break
-
-        if dashboard.datasources and not has_access_:
-            flash(DashboardAccessDeniedError.message, "danger")
-            return redirect(DASHBOARD_LIST_URL)
-
-        dash_edit_perm = security_manager.is_owner(
-            dashboard
-        ) and security_manager.can_access("can_save_dash", "Superset")
-        edit_mode = (
-            request.args.get(utils.ReservedUrlParameters.EDIT_MODE.value) == "true"
-        )
-
-        standalone_mode = ReservedUrlParameters.is_standalone_mode()
 
         add_extra_log_payload(
             dashboard_id=dashboard.id,
             dashboard_version="v2",
-            dash_edit_perm=dash_edit_perm,
-            edit_mode=edit_mode,
+            dash_edit_perm=(

Review Comment:
   Inlined logic from above to improve readability.



##########
superset/utils/decorators.py:
##########
@@ -114,27 +111,3 @@ def wrapped(*args: Any, **kwargs: Any) -> Any:
 
 def on_security_exception(self: Any, ex: Exception) -> Response:
     return self.response(403, **{"message": utils.error_msg_from_exception(ex)})
-
-
-# noinspection PyPackageRequirements

Review Comment:
   This decorator seemed superfluous as said logic could be handled elsewhere. 



##########
superset/security/manager.py:
##########
@@ -1995,38 +2010,40 @@ def raise_for_user_activity_access(user_id: int) -> None:
     def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
         """
         Raise an exception if the user cannot access the dashboard.
-        This does not check for the required role/permission pairs,
-        it only concerns itself with entity relationships.
+
+        This does not check for the required role/permission pairs, it only concerns
+        itself with entity relationships.
 
         :param dashboard: Dashboard the user wants access to
         :raises DashboardAccessDeniedError: If the user cannot access the resource
         """
+
         # pylint: disable=import-outside-toplevel
         from superset import is_feature_enabled
         from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 
-        def has_rbac_access() -> bool:
-            if not is_feature_enabled("DASHBOARD_RBAC") or not dashboard.roles:

Review Comment:
   This was a tad hard to digest, but it essence is checking if RBAC is _disabled_. A more digestible form (used below) to check if RBAC is _enabled_ is, 
   
   ```python
   if is_feature_enabled("DASHBOARD_RBAC") and dashboard.roles:
       ...
   ```



##########
superset/views/core.py:
##########
@@ -1641,76 +1648,54 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
-        if not dashboard:
-            abort(404)
 
-        assert dashboard is not None
-
-        has_access_ = False
-        for datasource in dashboard.datasources:
-            datasource = DatasourceDAO.get_datasource(
-                datasource_type=DatasourceType(datasource.type),
-                datasource_id=datasource.id,
-                session=db.session(),
+        try:
+           dashboard = DashboardDAO.get_by_id_or_slug(dashboard_id_or_slug)
+        except DashboardNotFoundError:
+            abort(404)
+        except DashboardAccessDeniedError as ex:
+            return redirect_with_flash(
+                url="/dashboard/list/",
+                message=utils.error_msg_from_exception(ex),
+                category="danger",
             )
-            if datasource and security_manager.can_access_datasource(
-                datasource=datasource,
-            ):
-                has_access_ = True
-
-            if has_access_:
-                break
-
-        if dashboard.datasources and not has_access_:
-            flash(DashboardAccessDeniedError.message, "danger")
-            return redirect(DASHBOARD_LIST_URL)
-
-        dash_edit_perm = security_manager.is_owner(
-            dashboard
-        ) and security_manager.can_access("can_save_dash", "Superset")
-        edit_mode = (
-            request.args.get(utils.ReservedUrlParameters.EDIT_MODE.value) == "true"
-        )
-
-        standalone_mode = ReservedUrlParameters.is_standalone_mode()
 
         add_extra_log_payload(
             dashboard_id=dashboard.id,
             dashboard_version="v2",
-            dash_edit_perm=dash_edit_perm,
-            edit_mode=edit_mode,
+            dash_edit_perm=(
+                security_manager.is_owner(dashboard)
+                and security_manager.can_access("can_save_dash", "Superset")
+            ),
+            edit_mode=(
+                request.args.get(ReservedUrlParameters.EDIT_MODE.value) == "true"
+            ),
         )
 
-        bootstrap_data = {
-            "user": bootstrap_user_data(g.user, include_perms=True),
-            "common": common_bootstrap_payload(g.user),
-        }
-
         return self.render_template(
             "superset/spa.html",
             entry="spa",
-            # dashboard title is always visible
-            title=dashboard.dashboard_title,
+            title=dashboard.dashboard_title,  # dashboard title is always visible
             bootstrap_data=json.dumps(
-                bootstrap_data, default=utils.pessimistic_json_iso_dttm_ser
+                {

Review Comment:
   Inlined logic from above.



##########
superset/security/manager.py:
##########
@@ -1995,38 +2010,40 @@ def raise_for_user_activity_access(user_id: int) -> None:
     def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
         """
         Raise an exception if the user cannot access the dashboard.
-        This does not check for the required role/permission pairs,
-        it only concerns itself with entity relationships.
+
+        This does not check for the required role/permission pairs, it only concerns
+        itself with entity relationships.
 
         :param dashboard: Dashboard the user wants access to
         :raises DashboardAccessDeniedError: If the user cannot access the resource
         """
+
         # pylint: disable=import-outside-toplevel
         from superset import is_feature_enabled
         from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 
-        def has_rbac_access() -> bool:

Review Comment:
   This logic has been inlined below.



##########
superset/security/manager.py:
##########
@@ -1995,38 +2010,40 @@ def raise_for_user_activity_access(user_id: int) -> None:
     def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
         """
         Raise an exception if the user cannot access the dashboard.
-        This does not check for the required role/permission pairs,
-        it only concerns itself with entity relationships.
+
+        This does not check for the required role/permission pairs, it only concerns
+        itself with entity relationships.
 
         :param dashboard: Dashboard the user wants access to
         :raises DashboardAccessDeniedError: If the user cannot access the resource
         """
+
         # pylint: disable=import-outside-toplevel
         from superset import is_feature_enabled
         from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 
-        def has_rbac_access() -> bool:
-            if not is_feature_enabled("DASHBOARD_RBAC") or not dashboard.roles:
-                return True
-
-            return any(
-                dashboard_role.id
-                in [user_role.id for user_role in self.get_user_roles()]
-                for dashboard_role in dashboard.roles
-            )
-
         if self.is_guest_user() and dashboard.embedded:
-            can_access = self.has_guest_access(dashboard)
+            if self.has_guest_access(dashboard):
+                return
         else:
-            can_access = (
-                self.is_admin()
-                or self.is_owner(dashboard)
-                or (dashboard.published and has_rbac_access())

Review Comment:
   I'm struggling to grok how/why the published state alters things. Note the challenge is logic outlined here isn't the entire corpus of rules from an access standpoint given that the view also had additional (potentially conflicting) logic based on whether the user had access to the underlying datasets.
   
   For now I've actually left out the published state. Personally I feel the access rubric is already quite complex and rather difficult to grok as it pertains to embedded dashboards and the like. Additionally I'm not sure I agree with the logic with regards to RBAC when no roles are defined. Personally this feels like one shouldn't strictly have access as opposed to falling back to the legacy logic. Do dashboard owners et al. grok how this works?



##########
superset/security/manager.py:
##########
@@ -1995,38 +2010,40 @@ def raise_for_user_activity_access(user_id: int) -> None:
     def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
         """
         Raise an exception if the user cannot access the dashboard.
-        This does not check for the required role/permission pairs,
-        it only concerns itself with entity relationships.
+
+        This does not check for the required role/permission pairs, it only concerns
+        itself with entity relationships.
 
         :param dashboard: Dashboard the user wants access to
         :raises DashboardAccessDeniedError: If the user cannot access the resource
         """
+
         # pylint: disable=import-outside-toplevel
         from superset import is_feature_enabled
         from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 
-        def has_rbac_access() -> bool:
-            if not is_feature_enabled("DASHBOARD_RBAC") or not dashboard.roles:
-                return True
-
-            return any(
-                dashboard_role.id
-                in [user_role.id for user_role in self.get_user_roles()]
-                for dashboard_role in dashboard.roles
-            )
-
         if self.is_guest_user() and dashboard.embedded:
-            can_access = self.has_guest_access(dashboard)
+            if self.has_guest_access(dashboard):
+                return
         else:
-            can_access = (
-                self.is_admin()
-                or self.is_owner(dashboard)
-                or (dashboard.published and has_rbac_access())
-                or (not dashboard.published and not dashboard.roles)
-            )
+            if self.is_admin() or self.is_owner(dashboard):
+                return
+
+            # RBAC and legacy (datasource inferred) access controls.

Review Comment:
   The TL;DR is this check now includes logic for the legacy access controls which were based (for right or wrong) around the notion that a user has access to the dashboard if they can access at least one of the underlying datasets which backs the dashboard. Said logic previously resided in the dashboard view.



##########
superset/views/core.py:
##########
@@ -1641,76 +1648,54 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
-        if not dashboard:
-            abort(404)
 
-        assert dashboard is not None
-
-        has_access_ = False
-        for datasource in dashboard.datasources:
-            datasource = DatasourceDAO.get_datasource(
-                datasource_type=DatasourceType(datasource.type),
-                datasource_id=datasource.id,
-                session=db.session(),
+        try:
+           dashboard = DashboardDAO.get_by_id_or_slug(dashboard_id_or_slug)
+        except DashboardNotFoundError:
+            abort(404)
+        except DashboardAccessDeniedError as ex:
+            return redirect_with_flash(
+                url="/dashboard/list/",
+                message=utils.error_msg_from_exception(ex),
+                category="danger",
             )
-            if datasource and security_manager.can_access_datasource(
-                datasource=datasource,
-            ):
-                has_access_ = True
-
-            if has_access_:
-                break
-
-        if dashboard.datasources and not has_access_:
-            flash(DashboardAccessDeniedError.message, "danger")
-            return redirect(DASHBOARD_LIST_URL)
-
-        dash_edit_perm = security_manager.is_owner(
-            dashboard
-        ) and security_manager.can_access("can_save_dash", "Superset")
-        edit_mode = (
-            request.args.get(utils.ReservedUrlParameters.EDIT_MODE.value) == "true"
-        )
-
-        standalone_mode = ReservedUrlParameters.is_standalone_mode()
 
         add_extra_log_payload(
             dashboard_id=dashboard.id,
             dashboard_version="v2",
-            dash_edit_perm=dash_edit_perm,
-            edit_mode=edit_mode,
+            dash_edit_perm=(
+                security_manager.is_owner(dashboard)
+                and security_manager.can_access("can_save_dash", "Superset")
+            ),
+            edit_mode=(

Review Comment:
   Inlined logic from above.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] villebro commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "villebro (via GitHub)" <gi...@apache.org>.
villebro commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1227087058


##########
superset/security/manager.py:
##########
@@ -1995,38 +2010,40 @@ def raise_for_user_activity_access(user_id: int) -> None:
     def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
         """
         Raise an exception if the user cannot access the dashboard.
-        This does not check for the required role/permission pairs,
-        it only concerns itself with entity relationships.
+
+        This does not check for the required role/permission pairs, it only concerns
+        itself with entity relationships.
 
         :param dashboard: Dashboard the user wants access to
         :raises DashboardAccessDeniedError: If the user cannot access the resource
         """
+
         # pylint: disable=import-outside-toplevel
         from superset import is_feature_enabled
         from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 
-        def has_rbac_access() -> bool:
-            if not is_feature_enabled("DASHBOARD_RBAC") or not dashboard.roles:
-                return True
-
-            return any(
-                dashboard_role.id
-                in [user_role.id for user_role in self.get_user_roles()]
-                for dashboard_role in dashboard.roles
-            )
-
         if self.is_guest_user() and dashboard.embedded:
-            can_access = self.has_guest_access(dashboard)
+            if self.has_guest_access(dashboard):
+                return
         else:
-            can_access = (
-                self.is_admin()
-                or self.is_owner(dashboard)
-                or (dashboard.published and has_rbac_access())

Review Comment:
   @john-bodley I kind of have a SIP brewing for a more universal object level RBAC model. I'll circulate it with you once I have the main design ready, as I'm also curious to hear your thoughts on it.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] villebro commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "villebro (via GitHub)" <gi...@apache.org>.
villebro commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1226210052


##########
tests/integration_tests/dashboards/security/security_dataset_tests.py:
##########
@@ -234,3 +235,4 @@ def test_get_dashboards_api_no_data_access(self):
         self.assert200(rv)
         data = json.loads(rv.data.decode("utf-8"))
         self.assertEqual(0, data["count"])
+        DashboardDAO.delete(dashboard)

Review Comment:
   nice 👍 One more down, I wonder how many to go..



##########
superset/security/manager.py:
##########
@@ -1995,38 +2010,40 @@ def raise_for_user_activity_access(user_id: int) -> None:
     def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
         """
         Raise an exception if the user cannot access the dashboard.
-        This does not check for the required role/permission pairs,
-        it only concerns itself with entity relationships.
+
+        This does not check for the required role/permission pairs, it only concerns
+        itself with entity relationships.
 
         :param dashboard: Dashboard the user wants access to
         :raises DashboardAccessDeniedError: If the user cannot access the resource
         """
+
         # pylint: disable=import-outside-toplevel
         from superset import is_feature_enabled
         from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 
-        def has_rbac_access() -> bool:
-            if not is_feature_enabled("DASHBOARD_RBAC") or not dashboard.roles:
-                return True
-
-            return any(
-                dashboard_role.id
-                in [user_role.id for user_role in self.get_user_roles()]
-                for dashboard_role in dashboard.roles
-            )
-
         if self.is_guest_user() and dashboard.embedded:
-            can_access = self.has_guest_access(dashboard)
+            if self.has_guest_access(dashboard):
+                return
         else:
-            can_access = (
-                self.is_admin()
-                or self.is_owner(dashboard)
-                or (dashboard.published and has_rbac_access())

Review Comment:
   > Personally this feels like one shouldn't strictly have access as opposed to falling back to the legacy logic. Do dashboard owners et al. grok how this works?
   
   The reason why it's like this is because otherwise it's impossible to support both regular RBAC and "dashboard RBAC" on the same running instance. I know it may be slightly confusing, but it's clearly documented, both in the modal and the docs, so changing this would be a pretty substantial breaking change.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225148844


##########
superset/security/manager.py:
##########
@@ -1995,38 +2010,38 @@ def raise_for_user_activity_access(user_id: int) -> None:
     def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
         """
         Raise an exception if the user cannot access the dashboard.
-        This does not check for the required role/permission pairs,
-        it only concerns itself with entity relationships.
+
+        This does not check for the required role/permission pairs, it only concerns
+        itself with entity relationships.
 
         :param dashboard: Dashboard the user wants access to
         :raises DashboardAccessDeniedError: If the user cannot access the resource
         """
+
         # pylint: disable=import-outside-toplevel
         from superset import is_feature_enabled
         from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 
-        def has_rbac_access() -> bool:
-            if not is_feature_enabled("DASHBOARD_RBAC") or not dashboard.roles:
-                return True
-
-            return any(
-                dashboard_role.id
-                in [user_role.id for user_role in self.get_user_roles()]
-                for dashboard_role in dashboard.roles
-            )
-
         if self.is_guest_user() and dashboard.embedded:
-            can_access = self.has_guest_access(dashboard)
+            if self.has_guest_access(dashboard):
+                return
         else:
-            can_access = (
-                self.is_admin()
-                or self.is_owner(dashboard)
-                or (dashboard.published and has_rbac_access())
-                or (not dashboard.published and not dashboard.roles)

Review Comment:
   Aren't dashboards roles only a thing if RBAC is enabled?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225026356


##########
superset/views/core.py:
##########
@@ -1641,76 +1648,54 @@ def favstar(  # pylint: disable=no-self-use
     @has_access
     @expose("/dashboard/<dashboard_id_or_slug>/")
     @event_logger.log_this_with_extra_payload
-    @check_dashboard_access(
-        on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
-    )
     def dashboard(
         self,
-        dashboard_id_or_slug: str,  # pylint: disable=unused-argument
+        dashboard_id_or_slug: str,
         add_extra_log_payload: Callable[..., None] = lambda **kwargs: None,
-        dashboard: Dashboard | None = None,
     ) -> FlaskResponse:
         """
-        Server side rendering for a dashboard
-        :param dashboard_id_or_slug: identifier for dashboard. used in the decorators
+        Server side rendering for a dashboard.
+
+        :param dashboard_id_or_slug: identifier for dashboard
         :param add_extra_log_payload: added by `log_this_with_manual_updates`, set a
             default value to appease pylint
-        :param dashboard: added by `check_dashboard_access`
         """
-        if not dashboard:
-            abort(404)
 
-        assert dashboard is not None
-
-        has_access_ = False
-        for datasource in dashboard.datasources:
-            datasource = DatasourceDAO.get_datasource(
-                datasource_type=DatasourceType(datasource.type),
-                datasource_id=datasource.id,
-                session=db.session(),
+        try:
+           dashboard = DashboardDAO.get_by_id_or_slug(dashboard_id_or_slug)
+        except DashboardNotFoundError:
+            abort(404)
+        except DashboardAccessDeniedError as ex:
+            return redirect_with_flash(
+                url="/dashboard/list/",
+                message=utils.error_msg_from_exception(ex),
+                category="danger",
             )
-            if datasource and security_manager.can_access_datasource(
-                datasource=datasource,
-            ):
-                has_access_ = True
-
-            if has_access_:
-                break
-
-        if dashboard.datasources and not has_access_:
-            flash(DashboardAccessDeniedError.message, "danger")
-            return redirect(DASHBOARD_LIST_URL)
-
-        dash_edit_perm = security_manager.is_owner(
-            dashboard
-        ) and security_manager.can_access("can_save_dash", "Superset")
-        edit_mode = (
-            request.args.get(utils.ReservedUrlParameters.EDIT_MODE.value) == "true"
-        )
-
-        standalone_mode = ReservedUrlParameters.is_standalone_mode()
 
         add_extra_log_payload(
             dashboard_id=dashboard.id,
             dashboard_version="v2",
-            dash_edit_perm=dash_edit_perm,
-            edit_mode=edit_mode,
+            dash_edit_perm=(
+                security_manager.is_owner(dashboard)
+                and security_manager.can_access("can_save_dash", "Superset")
+            ),
+            edit_mode=(

Review Comment:
   Inlined logic from above.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225061550


##########
superset/security/manager.py:
##########
@@ -1995,38 +2010,40 @@ def raise_for_user_activity_access(user_id: int) -> None:
     def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
         """
         Raise an exception if the user cannot access the dashboard.
-        This does not check for the required role/permission pairs,
-        it only concerns itself with entity relationships.
+
+        This does not check for the required role/permission pairs, it only concerns
+        itself with entity relationships.
 
         :param dashboard: Dashboard the user wants access to
         :raises DashboardAccessDeniedError: If the user cannot access the resource
         """
+
         # pylint: disable=import-outside-toplevel
         from superset import is_feature_enabled
         from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
 
-        def has_rbac_access() -> bool:
-            if not is_feature_enabled("DASHBOARD_RBAC") or not dashboard.roles:
-                return True
-
-            return any(
-                dashboard_role.id
-                in [user_role.id for user_role in self.get_user_roles()]
-                for dashboard_role in dashboard.roles
-            )
-
         if self.is_guest_user() and dashboard.embedded:
-            can_access = self.has_guest_access(dashboard)
+            if self.has_guest_access(dashboard):
+                return
         else:
-            can_access = (
-                self.is_admin()
-                or self.is_owner(dashboard)
-                or (dashboard.published and has_rbac_access())

Review Comment:
   I'm struggling to grok how/why the published state alters things. Note the challenge is logic outlined here isn't the entire corpus of rules from an access standpoint given that the view also had additional (potentially conflicting) logic based on whether the user had access to the underlying datasets.
   
   Personally I feel the access rubric is already quite complex and rather difficult to grok as it pertains to embedded dashboards and the like. Additionally I'm not sure I agree with the logic with regards to RBAC when no roles are defined. Personally this feels like one shouldn't strictly have access as opposed to falling back to the legacy logic. Do dashboard owners et al. grok how this works?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] john-bodley commented on a diff in pull request #24350: fix: Address dashboard permission regression in #23586

Posted by "john-bodley (via GitHub)" <gi...@apache.org>.
john-bodley commented on code in PR #24350:
URL: https://github.com/apache/superset/pull/24350#discussion_r1225581865


##########
tests/integration_tests/dashboards/api_tests.py:
##########
@@ -506,43 +463,6 @@ def test_get_dashboard_no_data_access(self):
         db.session.delete(dashboard)
         db.session.commit()
 
-    def test_get_draft_dashboard_without_roles_by_uuid(self):

Review Comment:
   Moved to RBAC tests given dashboard roles are only associated with RBAC.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org