-
Notifications
You must be signed in to change notification settings - Fork 14.3k
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: Dashboard aware RBAC dataset permission #24789
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -169,7 +169,6 @@ def test_get_dashboard_view__user_access_with_dashboard_permission(self): | |
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 @@ def test_get_dashboard_view__user_access_with_dashboard_permission(self): | |
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test (alongside others) was failing after the change which likely indicates that either i) RBAC was supposed to expand beyond the scope/context of the dashboard (and thus my understanding of said issue was misconstrued), or ii) was a mistake. |
||
|
||
# post | ||
revoke_access_to_dashboard(dashboard_to_access, new_role) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -159,41 +159,6 @@ def test_has_guest_access__unauthorized_guest_user__different_resource_type(self | |
has_guest_access = security_manager.has_guest_access(self.dash) | ||
self.assertFalse(has_guest_access) | ||
|
||
def test_chart_raise_for_access_as_guest(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Conditional on my logic being correct, these tests are now superfluous as the irrespective of whether a guest is authorized or not, they don't have access to the chart, dataset, etc. unless it's in the context of RBAC. The logic previously outlined here condenses to the same logic which is defined in the |
||
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 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,7 @@ | |
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 @@ def test_raise_for_access_query(self, mock_can_access, mock_is_owner): | |
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,24 +1677,64 @@ def test_raise_for_access_table(self, mock_can_access): | |
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 | ||
|
||
with self.assertRaises(SupersetSecurityException): | ||
security_manager.raise_for_access(viz=test_viz) | ||
|
||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure this is needed, i.e., |
||
@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") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not too familiar with this feature either, but just noticed that the previous version checks for datasource access and the new one checks for dashboard access only. Is that what we want?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eschutho the logic is as follows. If you don't have explicit access to dataset you then fallback to the form-data of either the visualization or query-context to try to decipher the context. If it's in the context of a dashboard, i.e., you're viewing a chart in a dashboard, one checks whether you have access to the dashboard—per the
can_access_dashboard
method.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to check that the purported chart whose data is being loaded actually exists on the dashboard?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@villebro is the logic on line #1846 not suffice, i.e., extracting the dashboard ID from the form-data (which could stem from either a legacy visualization or query-context)?