Skip to content

fix(dashboard-export): include and re-attach roles in import/export (#21000)#40136

Open
rusackas wants to merge 3 commits into
masterfrom
tdd/issue-21000-export-roles
Open

fix(dashboard-export): include and re-attach roles in import/export (#21000)#40136
rusackas wants to merge 3 commits into
masterfrom
tdd/issue-21000-export-roles

Conversation

@rusackas
Copy link
Copy Markdown
Member

@rusackas rusackas commented May 15, 2026

SUMMARY

Closes #21000.

Dashboards restricted via DASHBOARD_RBAC had their role assignments silently dropped from export bundles, so a round-trip recreated the dashboard with no access restriction — a "least privilege" dashboard became "all roles can access" on import. Dashboard.export_fields doesn't include roles (it's a many-to-many relationship, not a column), so export_to_dict skipped it entirely.

This PR:

  1. Export (export.py:_file_content): emit roles as a list of role names in the YAML when the dashboard has any. Names rather than IDs because IDs are environment-local — names are the only thing that can cross environments. Omits the key entirely when there are no role restrictions (older import code treats "missing" as "no restriction"; an empty list could confuse importers that distinguish the two states).

  2. Import (importers/v1/utils.py:import_dashboard): pop roles before handing config to import_from_dict (the standard SQLAlchemy path doesn't resolve role names into role objects), then resolve each name via security_manager.find_role and assign dashboard.roles after creation. Roles that don't exist in the destination are skipped with a warning rather than failing the whole import — admins may need to create them before the access restriction takes effect.

  3. Locks in regression tests (added in this PR's earlier commit):

    • test_file_content_includes_roles_for_dashboard_with_role_restrictions
    • test_file_content_omits_roles_field_when_dashboard_has_no_roles (strict — "roles" not in result)

Security note

This is a security improvement: a round-trip that silently lost role restrictions is the kind of footgun that turns a "least privilege" config into a "public" one without any visible change. Bundles that already exist in the wild without a roles key continue to import as "no restriction," matching prior behavior. Bundles with a roles key are interpreted as a restriction request.

TESTING INSTRUCTIONS

pytest tests/unit_tests/commands/dashboard/export_test.py::test_file_content_includes_roles_for_dashboard_with_role_restrictions -v
pytest tests/unit_tests/commands/dashboard/export_test.py::test_file_content_omits_roles_field_when_dashboard_has_no_roles -v

ADDITIONAL INFORMATION

🤖 Generated with Claude Code

Closes #21000

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dosubot dosubot Bot added authentication:RBAC Related to RBAC dashboard:export Related to exporting dashboards dashboard:security:access Related to the security access of the Dashboard labels May 15, 2026
@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented May 15, 2026

Code Review Agent Run #3edc95

Actionable Suggestions - 0
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • tests/unit_tests/commands/dashboard/export_test.py - 1
Review Details
  • Files reviewed - 1 · Commit Range: ce6e9f4..ce6e9f4
    • tests/unit_tests/commands/dashboard/export_test.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Comment thread tests/unit_tests/commands/dashboard/export_test.py Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

❌ Patch coverage is 16.66667% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.16%. Comparing base (2b71d96) to head (90e86ae).

Files with missing lines Patch % Lines
superset/commands/dashboard/importers/v1/utils.py 11.11% 7 Missing and 1 partial ⚠️
superset/commands/dashboard/export.py 33.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #40136      +/-   ##
==========================================
- Coverage   64.17%   64.16%   -0.01%     
==========================================
  Files        2590     2590              
  Lines      138087   138099      +12     
  Branches    32039    32043       +4     
==========================================
+ Hits        88615    88617       +2     
- Misses      47947    47955       +8     
- Partials     1525     1527       +2     
Flag Coverage Δ
hive 39.46% <0.00%> (-0.01%) ⬇️
mysql 59.17% <16.66%> (-0.01%) ⬇️
postgres 59.24% <16.66%> (-0.01%) ⬇️
presto 41.15% <0.00%> (-0.01%) ⬇️
python 60.68% <16.66%> (-0.01%) ⬇️
sqlite 58.88% <16.66%> (-0.01%) ⬇️
unit 100.00% <ø> (ø)

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

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread tests/unit_tests/commands/dashboard/export_test.py Outdated
@bito-code-review
Copy link
Copy Markdown
Contributor

The flagged issue is correct: the test's docstring states that dashboards with no roles 'must not emit an empty roles key', but the assertion allows both missing 'roles' and 'roles: []', contradicting the requirement. To resolve, update the assertion to strictly check that 'roles' is not in the result, ensuring no 'roles' key is emitted for unrestricted dashboards.

tests/unit_tests/commands/dashboard/export_test.py

result = yaml.safe_load(content)
    assert "roles" not in result

claude added 2 commits May 14, 2026 19:39
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dashboards restricted via `DASHBOARD_RBAC` had their role assignments
silently dropped from export bundles, so a round-trip recreated the
dashboard with no access restriction — a 'least privilege' dashboard
became 'all roles can access' on import. `Dashboard.export_fields`
doesn't include `roles` (it's a many-to-many relationship, not a
column), so `export_to_dict` skipped it entirely.

This change:

- **Export** (`export.py:_file_content`): emit `roles` as a list of
  role *names* in the YAML when the dashboard has any. Names rather
  than IDs because IDs are environment-local — names are the only
  thing that can cross environments. Omits the key entirely when
  there are no role restrictions (older import code treats "missing"
  as "no restriction"; an empty list could confuse importers that
  distinguish the two states).

- **Import** (`importers/v1/utils.py:import_dashboard`): pop `roles`
  before handing config to `import_from_dict` (the standard SQLAlchemy
  path doesn't resolve role names into role objects), then resolve each
  name via `security_manager.find_role` and assign `dashboard.roles`
  after creation. Roles that don't exist in the destination are skipped
  with a warning rather than failing the whole import.

Companion regression tests (added in this PR's earlier commit) cover
both the populated-roles and no-roles cases on the export side.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@pull-request-size pull-request-size Bot added size/L and removed size/M labels May 15, 2026
@rusackas rusackas changed the title test(dashboard-export): assert roles included in export bundle (#21000) fix(dashboard-export): include and re-attach roles in import/export (#21000) May 15, 2026
@rusackas rusackas requested a review from sadpandajoe May 15, 2026 08:16
@bito-code-review
Copy link
Copy Markdown
Contributor

Bito Automatic Review Failed - Technical Failure

Bito encountered technical difficulties while generating code feedback . To retry, type /review in a comment and save. If the issue persists, contact support@bito.ai and provide the following details:

Agent Run ID: 535fedae-7df0-415a-8f6e-ba54a9830576

@SBIN2010
Copy link
Copy Markdown
Contributor

Hi @rusackas
When manually testing, an error appears during import.
Снимок экрана от 2026-05-25 00-57-06

ERROR:superset.commands.importers.v1.utils:Schema validation failed for dashboards/Unicode_Test_25.yaml (prefix: dashboards): {'roles': ['Unknown field.']}
ERROR:superset.commands.importers.v1:Validation failed for dashboards/Unicode_Test_25.yaml: {'roles': ['Unknown field.']}
INFO:superset.commands.dashboard.importers.dispatcher:Command failed validation
WARNING:superset.views.error_handling:CommandException
Traceback (most recent call last):
File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1484, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1469, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/.venv/lib/python3.11/site-packages/flask_appbuilder/security/decorators.py", line 128, in wraps
return f(self, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/superset/views/base_api.py", line 127, in wraps
duration, response = time_function(f, self, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/superset/utils/core.py", line 1561, in time_function
response = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/app/superset/utils/log.py", line 301, in wrapper
value = f(*args, **kwargs)
^^^^^^^^^^^^^^^^^^
File "/app/superset/views/base_api.py", line 114, in wraps
return f(self, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/superset/dashboards/api.py", line 1966, in import_
command.run()
File "/app/superset/commands/dashboard/importers/dispatcher.py", line 57, in run
command.run()
File "/app/superset/utils/decorators.py", line 267, in wrapped
return on_error(ex)
^^^^^^^^^^^^
File "/app/superset/utils/decorators.py", line 232, in on_error
raise ex
File "/app/superset/utils/decorators.py", line 260, in wrapped
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/app/superset/commands/importers/v1/init.py", line 87, in run
self.validate()
File "/app/superset/commands/importers/v1/init.py", line 134, in validate
raise CommandInvalidError(
superset.commands.exceptions.CommandInvalidError: Error importing dashboard: dashboards/Unicode_Test_25.yaml: {'roles': ['Unknown field.']}

Schema validation fails

need to be added for ImportV1DashboardSchema(Schema)
roles = fields.List(fields.String(), allow_none=True)

@rusackas
Copy link
Copy Markdown
Member Author

Ahh, interesting. Thanks for testing. I'll investigate soon, but feel free to push a commit if you spit the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

authentication:RBAC Related to RBAC dashboard:export Related to exporting dashboards dashboard:security:access Related to the security access of the Dashboard preset-io size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard/Assets Export not exporting Roles

3 participants