Skip to content

Commit 5909e90

Browse files
rusackasclaude
andauthored
feat(security): add built-in Public role for anonymous dashboard access (apache#36548)
Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent dcc556a commit 5909e90

File tree

10 files changed

+518
-207
lines changed

10 files changed

+518
-207
lines changed

RESOURCES/STANDARD_ROLES.md

Lines changed: 190 additions & 189 deletions
Large diffs are not rendered by default.

docs/docs/configuration/networking-settings.mdx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,20 @@ Restart Superset for this configuration change to take effect.
5151

5252
#### Making a Dashboard Public
5353

54-
1. Add the `'DASHBOARD_RBAC': True` [Feature Flag](https://github.com/apache/superset/blob/master/RESOURCES/FEATURE_FLAGS.md) to `superset_config.py`
55-
2. Add the `Public` role to your dashboard as described [here](https://superset.apache.org/docs/using-superset/creating-your-first-dashboard/#manage-access-to-dashboards)
54+
There are two approaches to making dashboards publicly accessible:
55+
56+
**Option 1: Dataset-based access (simpler)**
57+
1. Set `PUBLIC_ROLE_LIKE = "Public"` in `superset_config.py`
58+
2. Grant the Public role access to the relevant datasets (Menu → Security → List Roles → Public)
59+
3. All published dashboards using those datasets become visible to anonymous users
60+
61+
**Option 2: Dashboard-level access (selective control)**
62+
1. Set `PUBLIC_ROLE_LIKE = "Public"` in `superset_config.py`
63+
2. Add the `'DASHBOARD_RBAC': True` [Feature Flag](https://github.com/apache/superset/blob/master/RESOURCES/FEATURE_FLAGS.md)
64+
3. Edit each dashboard's properties and add the "Public" role
65+
4. Only dashboards with the Public role explicitly assigned are visible to anonymous users
66+
67+
See the [Public role documentation](/docs/security/security#public) for more details.
5668

5769
#### Embedding a Public Dashboard
5870

docs/docs/security/security.mdx

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,62 @@ to all databases by default, both **Alpha** and **Gamma** users need to be given
4646

4747
### Public
4848

49-
To allow logged-out users to access some Superset features, you can use the `PUBLIC_ROLE_LIKE` config setting and assign it to another role whose permissions you want passed to this role.
49+
The **Public** role is the most restrictive built-in role, designed specifically for anonymous/unauthenticated
50+
users who need to view dashboards. It provides minimal read-only access for:
51+
52+
- Viewing dashboards and charts
53+
- Using interactive dashboard filters
54+
- Accessing dashboard and chart permalinks
55+
- Reading embedded dashboards
56+
- Viewing annotations on charts
57+
58+
The Public role explicitly excludes:
59+
- Any write permissions on dashboards, charts, or datasets
60+
- SQL Lab access
61+
- Share functionality
62+
- User profile or admin features
63+
- Menu access to most Superset features
64+
65+
Anonymous users are automatically assigned the Public role when `AUTH_ROLE_PUBLIC` is configured
66+
(a Flask-AppBuilder setting). The `PUBLIC_ROLE_LIKE` setting is **optional** and controls what
67+
permissions are synced to the Public role when you run `superset init`:
5068

51-
For example, by setting `PUBLIC_ROLE_LIKE = "Gamma"` in your `superset_config.py` file, you grant
52-
public role the same set of permissions as for the **Gamma** role. This is useful if one
53-
wants to enable anonymous users to view dashboards. Explicit grant on specific datasets is
54-
still required, meaning that you need to edit the **Public** role and add the public data sources to the role manually.
69+
```python
70+
# Optional: Sync sensible default permissions to the Public role
71+
PUBLIC_ROLE_LIKE = "Public"
72+
73+
# Alternative: Copy permissions from Gamma for broader access
74+
# PUBLIC_ROLE_LIKE = "Gamma"
75+
```
76+
77+
If you prefer to manually configure the Public role's permissions (or use `DASHBOARD_RBAC` to
78+
grant access at the dashboard level), you do not need to set `PUBLIC_ROLE_LIKE`.
79+
80+
**Important notes:**
81+
82+
- **Data access is still required:** The Public role only grants UI/API permissions. You must
83+
also grant access to specific datasets necessary to view a dashboard. As with other roles,
84+
this can be done in two ways:
85+
86+
- **Without `DASHBOARD_RBAC`:** Dashboards only appear in the list and are accessible if
87+
the user has permission to at least one of their datasets. Grant dataset access by editing
88+
the Public role in the Superset UI (Menu → Security → List Roles → Public) and adding the
89+
relevant data sources. All published dashboards using those datasets become visible.
90+
91+
- **With `DASHBOARD_RBAC` enabled:** Anonymous users will only see dashboards where the
92+
"Public" role has been explicitly added in the dashboard's properties. Dataset permissions
93+
are not required—DASHBOARD_RBAC handles the cascading permissions check. This provides
94+
fine-grained control over which dashboards are publicly visible.
95+
96+
- **Role synchronization:** Built-in role permissions (Admin, Alpha, Gamma, sql_lab, and Public
97+
when `PUBLIC_ROLE_LIKE = "Public"`) are synchronized when you run `superset init`. Any manual
98+
permission edits to these roles may be overwritten during upgrades. To customize the Public
99+
role permissions, you can either:
100+
- Edit the Public role directly and avoid setting `PUBLIC_ROLE_LIKE` (permissions won't be
101+
overwritten by `superset init`)
102+
- Copy the Public role via "Copy Role" in the Superset web UI, save it under a different name
103+
(e.g., "Public_Custom"), customize the permissions, then update **both** configs:
104+
`PUBLIC_ROLE_LIKE = "Public_Custom"` and `AUTH_ROLE_PUBLIC = "Public_Custom"`
55105

56106
### Managing Data Source Access for Gamma Roles
57107

@@ -64,6 +114,46 @@ tables in the **Permissions** dropdown. To select the data sources you want to a
64114
You can then confirm with users assigned to the **Gamma** role that they see the
65115
objects (dashboards and slices) associated with the tables you just extended them.
66116

117+
### Dashboard Access Control
118+
119+
Access to dashboards is managed via owners (users that have edit permissions to the dashboard).
120+
Non-owner user access can be managed in two ways. Note that dashboards must be published to be
121+
visible to other users.
122+
123+
#### Dataset-Based Access (Default)
124+
125+
By default, users can view published dashboards if they have access to at least one dataset
126+
used in that dashboard. Grant dataset access by adding the relevant data source permissions
127+
to a role (Menu → Security → List Roles).
128+
129+
This is the simplest approach but provides all-or-nothing access based on dataset permissions—
130+
if a user has access to a dataset, they can see all published dashboards using that dataset.
131+
132+
#### Dashboard-Level Access (DASHBOARD_RBAC)
133+
134+
For fine-grained control over which dashboards specific roles can access, enable the
135+
`DASHBOARD_RBAC` feature flag:
136+
137+
```python
138+
FEATURE_FLAGS = {
139+
"DASHBOARD_RBAC": True,
140+
}
141+
```
142+
143+
With this enabled, you can assign specific roles to each dashboard in its properties. Users
144+
will only see dashboards where their role is explicitly added.
145+
146+
**Important considerations:**
147+
- Dashboard access **bypasses** dataset-level checks—granting a role access to a dashboard
148+
implicitly grants read access to all charts and datasets in that dashboard
149+
- Dashboards without any assigned roles fall back to dataset-based access
150+
- The dashboard must still be published to be visible
151+
152+
This feature is particularly useful for:
153+
- Making specific dashboards public while keeping others private
154+
- Granting access to dashboards without exposing the underlying datasets for other uses
155+
- Creating dashboard-specific access patterns that don't align with dataset ownership
156+
67157
### SQL Execution Security Considerations
68158

69159
Apache Superset includes features designed to provide safeguards when interacting with connected databases, such as the `DISALLOWED_SQL_FUNCTIONS` configuration setting. This aims to prevent the execution of potentially harmful database functions or system variables directly from Superset interfaces like SQL Lab.

docs/docs/using-superset/creating-your-first-dashboard.mdx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,12 @@ slices and dashboards of your own.
183183

184184
### Manage access to Dashboards
185185

186-
Access to dashboards is managed via owners (users that have edit permissions to the dashboard).
186+
Access to dashboards is managed via owners and permissions. Non-owner access can be controlled
187+
through dataset permissions or dashboard-level roles (using the `DASHBOARD_RBAC` feature flag).
187188

188-
Non-owner users access can be managed in two different ways. The dashboard needs to be published to be visible to other users.
189-
190-
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.
191-
2. Dashboard roles - if you enable [**DASHBOARD_RBAC** feature flag](/docs/configuration/configuring-superset#feature-flags) then you will be able to manage which roles can access the dashboard
192-
- 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.
193-
- If no roles are specified for a dashboard, regular **Dataset permissions** will apply.
189+
For detailed information on configuring dashboard access, see the
190+
[Dashboard Access Control](/docs/security/security#dashboard-access-control) section in the
191+
Security documentation.
194192

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

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ select = [
334334

335335
ignore = [
336336
"S101",
337+
"PT004", # Fixtures that don't return values - underscore prefix conflicts with pytest usage
337338
"PT006",
338339
"T201",
339340
"N999",

superset/security/manager.py

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
289289
}
290290

291291
GAMMA_READ_ONLY_MODEL_VIEWS = {
292+
"CssTemplate",
292293
"Dataset",
293294
"Datasource",
294295
} | READ_ONLY_MODEL_VIEWS
@@ -327,7 +328,6 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
327328
"Annotation",
328329
"CSS Templates",
329330
"ColumnarToDatabaseView",
330-
"CssTemplate",
331331
"ExcelToDatabaseView",
332332
"Import dashboards",
333333
"ImportExportRestApi",
@@ -414,6 +414,60 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
414414
("can_read", "Database"),
415415
}
416416

417+
# Permissions for the Public role - minimal read-only access for viewing
418+
# dashboards without authentication. This is more restrictive than Gamma.
419+
# Users can set PUBLIC_ROLE_LIKE = "Public" to use these sensible defaults.
420+
PUBLIC_ROLE_PERMISSIONS = {
421+
# Core dashboard viewing
422+
("can_read", "Dashboard"),
423+
("can_read", "Chart"),
424+
("can_dashboard", "Superset"),
425+
("can_slice", "Superset"),
426+
("can_explore_json", "Superset"),
427+
("can_dashboard_permalink", "Superset"),
428+
("can_read", "DashboardPermalinkRestApi"),
429+
# Dashboard filter interactions
430+
("can_read", "DashboardFilterStateRestApi"),
431+
("can_write", "DashboardFilterStateRestApi"),
432+
# API access for chart rendering
433+
("can_time_range", "Api"),
434+
("can_query_form_data", "Api"),
435+
("can_query", "Api"),
436+
# CSS for dashboard styling
437+
("can_read", "CssTemplate"),
438+
# Embedded dashboard support
439+
("can_read", "EmbeddedDashboard"),
440+
# Datasource metadata for chart rendering
441+
("can_get", "Datasource"),
442+
("can_external_metadata", "Datasource"),
443+
# Annotations on charts
444+
("can_read", "Annotation"),
445+
("can_read", "AnnotationLayerRestApi"),
446+
# Chart permalinks (for shared chart links)
447+
("can_read", "ExplorePermalinkRestApi"),
448+
}
449+
450+
# View menus that Public role should NOT have access to
451+
PUBLIC_EXCLUDED_VIEW_MENUS = {
452+
"SQL Lab",
453+
"SQL Editor",
454+
"Saved Queries",
455+
"Query Search",
456+
"Queries",
457+
"Security",
458+
"List Users",
459+
"List Roles",
460+
"Row Level Security",
461+
"Row Level Security Filters",
462+
"Access Requests",
463+
"Action Log",
464+
"Manage",
465+
"Import dashboards",
466+
"Annotation Layers",
467+
"CSS Templates",
468+
"Alerts & Report",
469+
}
470+
417471
data_access_permissions = (
418472
"database_access",
419473
"schema_access",
@@ -1230,9 +1284,18 @@ def sync_role_definitions(self) -> None:
12301284
self.set_role("sql_lab", self._is_sql_lab_pvm, pvms)
12311285

12321286
# Configure public role
1233-
if get_conf()["PUBLIC_ROLE_LIKE"]:
1287+
# If PUBLIC_ROLE_LIKE is "Public", use the built-in Public role with
1288+
# sensible defaults for anonymous dashboard viewing.
1289+
# If set to another role name (e.g., "Gamma"), copy permissions from that role.
1290+
# If not set (None), the Public role remains empty (default/legacy behavior).
1291+
public_role_like = get_conf()["PUBLIC_ROLE_LIKE"]
1292+
if public_role_like == "Public":
1293+
# Use the built-in Public role with minimal read-only permissions
1294+
self.set_role("Public", self._is_public_pvm, pvms)
1295+
elif public_role_like:
1296+
# Copy permissions from another role (e.g., "Gamma") to Public
12341297
self.copy_role(
1235-
get_conf()["PUBLIC_ROLE_LIKE"],
1298+
public_role_like,
12361299
self.auth_role_public,
12371300
merge=True,
12381301
)
@@ -1452,6 +1515,34 @@ def _is_sql_lab_pvm(self, pvm: PermissionView) -> bool:
14521515
in self.SQLLAB_EXTRA_PERMISSION_VIEWS
14531516
)
14541517

1518+
def _is_public_pvm(self, pvm: PermissionView) -> bool:
1519+
"""
1520+
Return True if the FAB permission/view is appropriate for the Public role,
1521+
False otherwise.
1522+
1523+
The Public role is designed for anonymous/unauthenticated users who need
1524+
to view dashboards. It provides minimal read-only access - more restrictive
1525+
than Gamma - suitable for public-facing dashboard deployments.
1526+
1527+
:param pvm: The FAB permission/view
1528+
:returns: Whether the FAB object is appropriate for Public role
1529+
"""
1530+
# Explicitly allow permissions in the PUBLIC_ROLE_PERMISSIONS set
1531+
if (pvm.permission.name, pvm.view_menu.name) in self.PUBLIC_ROLE_PERMISSIONS:
1532+
return True
1533+
1534+
# Exclude any view menus in the excluded list
1535+
if pvm.view_menu.name in self.PUBLIC_EXCLUDED_VIEW_MENUS:
1536+
return False
1537+
1538+
# Exclude user-defined permissions (datasource_access, schema_access, etc.)
1539+
# These must be explicitly granted to the Public role
1540+
if self._is_user_defined_permission(pvm):
1541+
return False
1542+
1543+
# Exclude all other permissions not explicitly allowed
1544+
return False
1545+
14551546
def database_after_insert(
14561547
self,
14571548
mapper: Mapper,

superset/views/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def sanitize_datasource_data(
107107
def bootstrap_user_data(user: User, include_perms: bool = False) -> dict[str, Any]:
108108
if user.is_anonymous:
109109
payload = {}
110-
user.roles = (security_manager.find_role("Public"),)
110+
user.roles = (security_manager.get_public_role(),)
111111
elif security_manager.is_guest_user(user):
112112
payload = {
113113
"username": user.username,

tests/integration_tests/fixtures/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
load_energy_table_with_slice,
2525
)
2626
from .public_role import ( # noqa: F401
27+
public_role_builtin,
2728
public_role_like_gamma,
2829
public_role_like_test_role,
2930
)

tests/integration_tests/fixtures/public_role.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,21 @@ def public_role_like_test_role(app_context: AppContext):
4141

4242
security_manager.get_public_role().permissions = []
4343
db.session.commit()
44+
45+
46+
@pytest.fixture
47+
def public_role_builtin(app_context: AppContext):
48+
"""
49+
Fixture that uses the built-in Public role with minimal read-only permissions.
50+
This sets PUBLIC_ROLE_LIKE to "Public" to use the new sensible defaults.
51+
"""
52+
original_value = app.config.get("PUBLIC_ROLE_LIKE")
53+
app.config["PUBLIC_ROLE_LIKE"] = "Public"
54+
security_manager.sync_role_definitions()
55+
56+
yield
57+
58+
# Restore original config and clean up
59+
app.config["PUBLIC_ROLE_LIKE"] = original_value
60+
security_manager.get_public_role().permissions = []
61+
db.session.commit()

0 commit comments

Comments
 (0)