Skip to content
This repository was archived by the owner on Nov 3, 2023. It is now read-only.

Commit fe2fe5a

Browse files
committed
Revert "fix(dashboard-rbac): use normal rbac when no roles chosen (apache#23586)"
This reverts commit a823033.
1 parent 8207813 commit fe2fe5a

File tree

8 files changed

+44
-107
lines changed

8 files changed

+44
-107
lines changed

docs/docs/creating-charts-dashboards/creating-your-first-dashboard.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,9 @@ Non-owner users access can be managed two different ways:
184184

185185
1. Dataset permissions - if you add to the relevant role permissions to datasets it automatically grants implicit access to all dashboards that uses those permitted datasets
186186
2. Dashboard roles - if you enable **DASHBOARD_RBAC** [feature flag](https://superset.apache.org/docs/installation/configuring-superset#feature-flags) then you be able to manage which roles can access the dashboard
187-
- Granting a role access to a dashboard will bypass dataset level checks. Having dashboard access implicitly grants read access to all the featured charts in the dashboard, and thereby also all the associated datasets.
188-
- If no roles are specified for a dashboard, regular **Dataset permissions** will apply.
187+
- Having dashboard access implicitly grants read access to the associated datasets, therefore
188+
all charts will load their data even if feature flag is turned on and no roles assigned
189+
to roles the access will fallback to **Dataset permissions**
189190

190191
<img src={useBaseUrl("/img/tutorial/tutorial_dashboard_access.png" )} />
191192

superset-frontend/src/dashboard/components/PropertiesModal/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ const PropertiesModal = ({
560560
</StyledFormItem>
561561
<p className="help-block">
562562
{t(
563-
'Roles is a list which defines access to the dashboard. Granting a role access to a dashboard will bypass dataset level checks. If no roles are defined, regular access permissions apply.',
563+
'Roles is a list which defines access to the dashboard. Granting a role access to a dashboard will bypass dataset level checks. If no roles are defined, then the dashboard is available to all roles.',
564564
)}
565565
</p>
566566
</Col>

superset/security/manager.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2046,10 +2046,7 @@ def raise_for_dashboard_access(self, dashboard: "Dashboard") -> None:
20462046
from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
20472047

20482048
def has_rbac_access() -> bool:
2049-
if not is_feature_enabled("DASHBOARD_RBAC") or not dashboard.roles:
2050-
return True
2051-
2052-
return any(
2049+
return (not is_feature_enabled("DASHBOARD_RBAC")) or any(
20532050
dashboard_role.id
20542051
in [user_role.id for user_role in self.get_user_roles()]
20552052
for dashboard_role in dashboard.roles
@@ -2076,18 +2073,15 @@ def can_access_based_on_dashboard(datasource: "BaseDatasource") -> bool:
20762073
from superset.models.dashboard import Dashboard
20772074
from superset.models.slice import Slice
20782075

2079-
dashboard_ids = db.session.query(Dashboard.id)
2080-
dashboard_ids = DashboardAccessFilter(
2081-
"id", SQLAInterface(Dashboard, db.session)
2082-
).apply(dashboard_ids, None)
2083-
20842076
datasource_class = type(datasource)
20852077
query = (
2086-
db.session.query(Dashboard.id)
2087-
.join(Slice, Dashboard.slices)
2078+
db.session.query(datasource_class)
20882079
.join(Slice.table)
20892080
.filter(datasource_class.id == datasource.id)
2090-
.filter(Dashboard.id.in_(dashboard_ids))
2081+
)
2082+
2083+
query = DashboardAccessFilter("id", SQLAInterface(Dashboard, db.session)).apply(
2084+
query, None
20912085
)
20922086

20932087
exists = db.session.query(query.exists()).scalar()

superset/utils/decorators.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ def on_security_exception(self: Any, ex: Exception) -> Response:
116116

117117

118118
# noinspection PyPackageRequirements
119-
def check_dashboard_access(on_error: Callable[[str], Any]) -> Callable[..., Any]:
119+
def check_dashboard_access(
120+
on_error: Callable[..., Any] = on_security_exception
121+
) -> Callable[..., Any]:
120122
def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
121123
@wraps(f)
122124
def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
@@ -128,7 +130,7 @@ def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
128130
try:
129131
current_app.appbuilder.sm.raise_for_dashboard_access(dashboard)
130132
except DashboardAccessDeniedError as ex:
131-
return on_error(str(ex))
133+
return on_error(self, ex)
132134
except Exception as exception:
133135
raise exception
134136

superset/views/core.py

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
TableColumn,
6767
)
6868
from superset.constants import QUERY_EARLY_CANCEL_KEY
69-
from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
7069
from superset.dashboards.commands.importers.v0 import ImportDashboardsCommand
7170
from superset.dashboards.dao import DashboardDAO
7271
from superset.dashboards.permalink.commands.get import GetDashboardPermalinkCommand
@@ -167,7 +166,6 @@
167166
get_form_data,
168167
get_viz,
169168
loads_request_json,
170-
redirect_with_flash,
171169
sanitize_datasource_data,
172170
)
173171
from superset.viz import BaseViz
@@ -193,7 +191,6 @@
193191
"disable_data_preview",
194192
]
195193

196-
DASHBOARD_LIST_URL = "/dashboard/list/"
197194
DATASOURCE_MISSING_ERR = __("The data source seems to have been deleted")
198195
USER_MISSING_ERR = __("The user seems to have been deleted")
199196
PARAMETER_MISSING_ERR = __(
@@ -1828,7 +1825,9 @@ def favstar( # pylint: disable=no-self-use
18281825
@expose("/dashboard/<dashboard_id_or_slug>/")
18291826
@event_logger.log_this_with_extra_payload
18301827
@check_dashboard_access(
1831-
on_error=lambda msg: redirect_with_flash(DASHBOARD_LIST_URL, msg, "danger")
1828+
on_error=lambda self, ex: Response(
1829+
utils.error_msg_from_exception(ex), status=403
1830+
)
18321831
)
18331832
def dashboard(
18341833
self,
@@ -1846,33 +1845,25 @@ def dashboard(
18461845
if not dashboard:
18471846
abort(404)
18481847

1849-
has_access_ = False
1850-
for datasource in dashboard.datasources:
1851-
datasource = DatasourceDAO.get_datasource(
1852-
datasource_type=DatasourceType(datasource.type),
1853-
datasource_id=datasource.id,
1854-
session=db.session(),
1855-
)
1856-
if datasource and security_manager.can_access_datasource(
1857-
datasource=datasource,
1858-
):
1859-
has_access_ = True
1860-
1861-
if has_access_ is False and config["ENABLE_ACCESS_REQUEST"]:
1862-
flash(
1863-
__(security_manager.get_datasource_access_error_msg(datasource)),
1864-
"danger",
1865-
)
1866-
return redirect(
1867-
f"/superset/request_access/?dashboard_id={dashboard.id}"
1848+
if config["ENABLE_ACCESS_REQUEST"]:
1849+
for datasource in dashboard.datasources:
1850+
datasource = DatasourceDAO.get_datasource(
1851+
datasource_type=DatasourceType(datasource.type),
1852+
datasource_id=datasource.id,
1853+
session=db.session(),
18681854
)
1869-
1870-
if has_access_:
1871-
break
1872-
1873-
if dashboard.datasources and not has_access_:
1874-
flash(DashboardAccessDeniedError.message, "danger")
1875-
return redirect(DASHBOARD_LIST_URL)
1855+
if datasource and not security_manager.can_access_datasource(
1856+
datasource=datasource
1857+
):
1858+
flash(
1859+
__(
1860+
security_manager.get_datasource_access_error_msg(datasource)
1861+
),
1862+
"danger",
1863+
)
1864+
return redirect(
1865+
f"/superset/request_access/?dashboard_id={dashboard.id}"
1866+
)
18761867

18771868
dash_edit_perm = security_manager.is_owner(
18781869
dashboard

superset/views/dashboard/mixin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class DashboardMixin: # pylint: disable=too-few-public-methods
6565
"roles": _(
6666
"Roles is a list which defines access to the dashboard. "
6767
"Granting a role access to a dashboard will bypass dataset level checks."
68-
"If no roles are defined, regular access permissions apply."
68+
"If no roles are defined then the dashboard is available to all roles."
6969
),
7070
"published": _(
7171
"Determines whether or not this dashboard is "

superset/views/utils.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,11 @@
2323
import msgpack
2424
import pyarrow as pa
2525
import simplejson as json
26-
from flask import flash, g, has_request_context, redirect, request
26+
from flask import g, has_request_context, request
2727
from flask_appbuilder.security.sqla import models as ab_models
2828
from flask_appbuilder.security.sqla.models import User
2929
from flask_babel import _
3030
from sqlalchemy.orm.exc import NoResultFound
31-
from werkzeug.wrappers.response import Response
3231

3332
import superset.models.core as models
3433
from superset import app, dataframe, db, result_set, viz
@@ -593,8 +592,3 @@ def get_cta_schema_name(
593592
if not func:
594593
return None
595594
return func(database, user, schema, sql)
596-
597-
598-
def redirect_with_flash(url: str, message: str, category: str) -> Response:
599-
flash(message=message, category=category)
600-
return redirect(url)

tests/integration_tests/dashboards/security/security_rbac_tests.py

Lines changed: 7 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,13 @@ def test_get_dashboard_view__user_can_not_access_without_permission(self):
9191

9292
# act
9393
response = self.get_dashboard_view_response(dashboard_to_access)
94-
assert response.status_code == 302
9594

9695
request_payload = get_query_context("birth_names")
9796
rv = self.post_assert_metric(CHART_DATA_URI, request_payload, "data")
98-
assert rv.status_code == 403
97+
self.assertEqual(rv.status_code, 403)
98+
99+
# assert
100+
self.assert403(response)
99101

100102
def test_get_dashboard_view__user_with_dashboard_permission_can_not_access_draft(
101103
self,
@@ -112,57 +114,11 @@ def test_get_dashboard_view__user_with_dashboard_permission_can_not_access_draft
112114
response = self.get_dashboard_view_response(dashboard_to_access)
113115

114116
# assert
115-
assert response.status_code == 302
117+
self.assert403(response)
116118

117119
# post
118120
revoke_access_to_dashboard(dashboard_to_access, new_role)
119121

120-
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
121-
def test_get_dashboard_view__user_no_access_regular_rbac(self):
122-
if backend() == "hive":
123-
return
124-
125-
slice = (
126-
db.session.query(Slice)
127-
.filter_by(slice_name="Girl Name Cloud")
128-
.one_or_none()
129-
)
130-
dashboard = create_dashboard_to_db(published=True, slices=[slice])
131-
self.login("gamma")
132-
133-
# assert redirect on regular rbac access denied
134-
response = self.get_dashboard_view_response(dashboard)
135-
assert response.status_code == 302
136-
137-
request_payload = get_query_context("birth_names")
138-
rv = self.post_assert_metric(CHART_DATA_URI, request_payload, "data")
139-
assert rv.status_code == 403
140-
db.session.delete(dashboard)
141-
db.session.commit()
142-
143-
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
144-
def test_get_dashboard_view__user_access_regular_rbac(self):
145-
if backend() == "hive":
146-
return
147-
148-
slice = (
149-
db.session.query(Slice)
150-
.filter_by(slice_name="Girl Name Cloud")
151-
.one_or_none()
152-
)
153-
dashboard = create_dashboard_to_db(published=True, slices=[slice])
154-
self.login("gamma_sqllab")
155-
156-
response = self.get_dashboard_view_response(dashboard)
157-
158-
assert response.status_code == 200
159-
160-
request_payload = get_query_context("birth_names")
161-
rv = self.post_assert_metric(CHART_DATA_URI, request_payload, "data")
162-
assert rv.status_code == 200
163-
db.session.delete(dashboard)
164-
db.session.commit()
165-
166122
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
167123
def test_get_dashboard_view__user_access_with_dashboard_permission(self):
168124
if backend() == "hive":
@@ -199,14 +155,13 @@ def test_get_dashboard_view__user_access_with_dashboard_permission(self):
199155
@pytest.mark.usefixtures("public_role_like_gamma")
200156
def test_get_dashboard_view__public_user_can_not_access_without_permission(self):
201157
dashboard_to_access = create_dashboard_to_db(published=True)
202-
grant_access_to_dashboard(dashboard_to_access, "Alpha")
203158
self.logout()
204159

205160
# act
206161
response = self.get_dashboard_view_response(dashboard_to_access)
207162

208163
# assert
209-
assert response.status_code == 302
164+
self.assert403(response)
210165

211166
@pytest.mark.usefixtures("public_role_like_gamma")
212167
def test_get_dashboard_view__public_user_with_dashboard_permission_can_not_access_draft(
@@ -220,7 +175,7 @@ def test_get_dashboard_view__public_user_with_dashboard_permission_can_not_acces
220175
response = self.get_dashboard_view_response(dashboard_to_access)
221176

222177
# assert
223-
assert response.status_code == 302
178+
self.assert403(response)
224179

225180
# post
226181
revoke_access_to_dashboard(dashboard_to_access, "Public")

0 commit comments

Comments
 (0)