Skip to content

perf(security): reuse permission answers within a dashboard load - #46026

Open
uhyunpark wants to merge 5 commits into
apache:masterfrom
uhyunpark:perf/dashboard-load-view-access-memo
Open

uhyunpark wants to merge 5 commits into
apache:masterfrom
uhyunpark:perf/dashboard-load-view-access-memo

Conversation

@uhyunpark

Copy link
Copy Markdown

SUMMARY

Loading a dashboard checks access to every member chart's datasource. Each check asks can_access the same few questions (all_datasource_access, all_database_access, then the database, catalog and schema permissions), and each answer is a query against the role tables. An admin passes on the first question; a viewer with access to few of the datasets goes through all of them, for every dataset.

On a dashboard of 72 charts over 72 datasets, a viewer granted one of them asks 77 distinct questions 1,155 times per request, on each of /dashboard/<id>, /charts and /datasets.

This PR lets can_access reuse its answers within those three requests:

  • remember_view_access, a new decorator, puts an empty memo on the request. can_access uses it only when present, so all other routes, CLI commands and Celery tasks ask every time.
  • The memo lives on the request object, not flask.g, and is keyed by the user object, so answers never cross requests or users.
  • On get it sits above @with_dashboard, so the lookup's own access check is covered.

Unchanged: every access decision. _has_view_access depends only on the user's roles (direct and through groups), the permission and the view menu, none of which change during these read-only requests. Only these permission answers are reused; each dataset is still checked on its own. Anonymous users still go through is_item_public.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A (no UI change). Measured on master with a SQLite metadata database: 72 charts on 72 virtual datasets, a Gamma viewer granted datasource_access on one of them, median of 5 calls per route.

Viewer request _has_view_access calls SQL statements Time
GET /api/v1/dashboard/<id> 1,155 → 77 1,248 → 170 199 → 33 ms
GET /api/v1/dashboard/<id>/charts 1,155 → 77 1,246 → 168 204 → 37 ms
GET /api/v1/dashboard/<id>/datasets 1,155 → 77 1,388 → 310 275 → 101 ms
  • The 77 left are one per distinct question: 72 per-dataset datasource_access, plus 5 shared.
  • Admin: unchanged, except /datasets (73 → 2 calls).
  • Response bodies are identical before and after, for both users.
  • Times are in-process SQLite. With a networked metadata database, each saved call is also a saved round trip.

TESTING INSTRUCTIONS

  • pytest tests/unit_tests/security/can_access_memo_test.py:
    • a repeated question is asked once within a dashboard load;
    • other requests, and code outside a request, ask every time;
    • answers don't cross users or requests;
    • the memo is on during the dashboard lookup for the three routes and off for /tabs; moving the decorator below @with_dashboard fails this test.
  • Security and dashboard unit suites pass.
  • Manually: as a Gamma user with access to one dataset of a dashboard with many charts, compare the timings of the three requests before and after.

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

🤖 Generated with Claude Code

A dashboard load asks can_access the same few questions once per member
dataset (all_datasource_access, all_database_access, the database, catalog
and schema permissions), and each answer is a query against the role
tables. On a dashboard of 72 virtual datasets, a viewer granted one of them
asked 77 distinct questions 1,155 times on each of /dashboard/<id>, /charts
and /datasets; an admin passes the first question.

remember_view_access puts a memo on the request, and can_access reuses its
answers, per user, for the rest of that request. Only the three
dashboard-load routes carry it; every other request, CLI command and task
asks each time. On get it sits above @with_dashboard, so the dashboard's own
access check is covered too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the api Related to the REST API label Nov 5, 2026
@protect()
@handle_api_exception
@statsd_metrics
@remember_view_access

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: After the first datasource check succeeds, this request-wide memo keeps later checks authorized even after a concurrent grant revocation, exposing subsequent dataset metadata.

Assessment: 🟠 Major · 🔁 Occurrence: Rarely · 🏷️ Security

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/dashboards/api.py
**Line:** 837:837
**Comment:**
	*Security: After the first datasource check succeeds, this request-wide memo keeps later checks authorized even after a concurrent grant revocation, exposing subsequent dataset metadata.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai-for-open-source

codeant-ai-for-open-source Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

CodeAnt  CodeAnt PR Risk

🟡  Medium Risk

  • The PR needs attention before merging because dashboard requests reuse cached access decisions.
  • A grant revoked during a request may remain effective for later checks, potentially exposing dashboard dataset metadata until the request ends.

Assessed commit: 21554057bc92

@codecov

codecov Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 65.42%. Comparing base (030a5a4) to head (2155405).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #46026   +/-   ##
=======================================
  Coverage   65.42%   65.42%           
=======================================
  Files        3082     3082           
  Lines      210951   210976   +25     
  Branches    49720    49724    +4     
=======================================
+ Hits       138006   138031   +25     
  Misses      69768    69768           
  Partials     3177     3177           
Flag Coverage Δ
hive 34.09% <55.55%> (+<0.01%) ⬆️
mysql 51.90% <100.00%> (+0.01%) ⬆️
postgres 51.93% <100.00%> (+0.01%) ⬆️
presto 35.74% <55.55%> (+<0.01%) ⬆️
python 52.22% <100.00%> (+0.01%) ⬆️
sqlite 51.61% <100.00%> (+0.01%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

@gabotorresruiz gabotorresruiz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @uhyunpark, nice improvement. I compared the three routes on master and on this branch across admin, Gamma with and without dataset grants (direct and via a group role), dashboard viewers, JWT, embedded guest tokens and anonymous with a Public role, and every status and body matched; the call counts in the description reproduce. CI is green. Two non-blocking notes inline.

Comment thread superset/security/manager.py Resolved
uhyunpark and others added 2 commits November 9, 2026 00:37
Under session auth FAB sets g.user to flask-login's current_user, a
single module-level proxy, so id(user) was the same for every user and
the owner check could not tell two users apart. can_access now unwraps
the proxy before keying the memo, and the cross-user test switches users
through login_user with g.user = current_user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/tabs looks the dashboard up twice (get_tabs_for_dashboard and
get_native_filter_configuration), and each lookup walks every member
dataset. With the memo the second walk is answered from the first. The
route test keeps a negative control through /embedded, which runs the
same lookup without the memo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@netlify

netlify Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 2155405
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6af256ccec827239108b1f7b
😎 Deploy Preview https://deploy-preview-46026--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@uhyunpark

Copy link
Copy Markdown
Author

Sorry for late, I was a bit busy in the weekend. I commented / updated my PR. Thanks for the review!

This branch has not been deployed

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

Labels

api Related to the REST API size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants