Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Address dashboard permission regression in #23586 #24350

Merged
merged 1 commit into from
Jun 12, 2023

Conversation

john-bodley
Copy link
Member

@john-bodley john-bodley commented Jun 10, 2023

SUMMARY

This PR addresses an issue in #23586 where previously the necessary dashboard access checks were a combination of logic which resided in the security manager as well as the view with conflicting logic. This was problematic say if a user was deemed to have access to a dashboard (from a security perspective) but was then denied within the view which relied on additional dataset access checks meaning it was near impossible to bypass with custom security logic.

This PR simply moves the entirety of the permission checks to the security manager.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

TESTING INSTRUCTIONS

CI.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • 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

@@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

if self.is_admin() or self.is_owner(dashboard):
return

# RBAC and legacy (datasource inferred) access controls.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

# pylint: disable=import-outside-toplevel
from superset import is_feature_enabled
from superset.dashboards.commands.exceptions import DashboardAccessDeniedError

def has_rbac_access() -> bool:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic has been inlined below.

# 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:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

if is_feature_enabled("DASHBOARD_RBAC") and dashboard.roles:
    ...

can_access = (
self.is_admin()
or self.is_owner(dashboard)
or (dashboard.published and has_rbac_access())
Copy link
Member Author

@john-bodley john-bodley Jun 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

@john-bodley john-bodley marked this pull request as ready for review June 10, 2023 05:26
@codecov
Copy link

codecov bot commented Jun 10, 2023

Codecov Report

Merging #24350 (6b9484f) into master (0e3f1f6) will decrease coverage by 0.02%.
The diff coverage is 72.72%.

❗ Current head 6b9484f differs from pull request most recent head 9bba03b. Consider uploading reports for the commit 9bba03b to get more accurate results

@@            Coverage Diff             @@
##           master   #24350      +/-   ##
==========================================
- Coverage   69.09%   69.07%   -0.02%     
==========================================
  Files        1903     1903              
  Lines       74608    74585      -23     
  Branches     8107     8107              
==========================================
- Hits        51550    51523      -27     
- Misses      20947    20951       +4     
  Partials     2111     2111              
Flag Coverage Δ
hive 53.79% <4.54%> (+<0.01%) ⬆️
mysql 79.37% <72.72%> (-0.03%) ⬇️
postgres 79.44% <72.72%> (-0.03%) ⬇️
presto 53.71% <4.54%> (+<0.01%) ⬆️
python 83.36% <72.72%> (-0.03%) ⬇️
sqlite 77.96% <72.72%> (-0.03%) ⬇️
unit 54.20% <4.54%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
superset/utils/decorators.py 96.22% <ø> (+1.78%) ⬆️
superset/security/manager.py 95.11% <64.70%> (-0.81%) ⬇️
superset/views/core.py 75.99% <100.00%> (-0.26%) ⬇️

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

self.is_admin()
or self.is_owner(dashboard)
or (dashboard.published and has_rbac_access())
or (not dashboard.published and not dashboard.roles)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@john-bodley john-bodley force-pushed the john-bodley--fix-23586 branch 3 times, most recently from 2642b41 to 65ca7ed Compare June 10, 2023 18:37
@@ -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):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

"""

dashboard = Dashboard.get(dashboard_id_or_slug)
Copy link
Member Author

@john-bodley john-bodley Jun 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is concerning that this is different than the DashboardDAO.get_by_id_or_slug 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 in terms of the security model will actually make the service more secure as it'll be easier to grok and enforce.

flash(DashboardAccessDeniedError.message, "danger")
return redirect(DASHBOARD_LIST_URL)

dash_edit_perm = security_manager.is_owner(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is inlined later for improved readability.

)

bootstrap_data = {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is inlined later.

@@ -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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously this was non-idempotent.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member

@villebro villebro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, this really cleans up this logic. As there's clearly a need for more advanced RBAC models to complement the default "datasource centric" RBAC model (I believe #10408 which introduced Dashboard RBAC is the most liked SIP of all time), it may be a good idea to consider improving/clarifying this functionality when we start considering breaking changes for 4.0. I'd personally like to see the possibility of also separating charts from the datasource permission model, so that one could control who can see certain charts, irrespective of which datasets they're referencing.

@@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

can_access = (
self.is_admin()
or self.is_owner(dashboard)
or (dashboard.published and has_rbac_access())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@michael-s-molina
Copy link
Member

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 column in our board.

@john-bodley john-bodley merged commit a3aacf2 into apache:master Jun 12, 2023
@mistercrunch mistercrunch added 🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels 🚢 3.0.0 labels Mar 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels size/L 🚢 3.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants