Skip to content

fix(auth): redirect to login on failure to access dashboard permalink#40769

Open
dmunozv04 wants to merge 2 commits into
apache:masterfrom
dmunozv04:auth-dashboard-permalink-login
Open

fix(auth): redirect to login on failure to access dashboard permalink#40769
dmunozv04 wants to merge 2 commits into
apache:masterfrom
dmunozv04:auth-dashboard-permalink-login

Conversation

@dmunozv04
Copy link
Copy Markdown
Contributor

SUMMARY

Fixes #40768, where accessing a permalink to a private dashboard without being logged in would return an error instead of redirecting to the login page.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Before: {"error": "Error: You don't have access to this dashboard."}
After: redirect to login page

TESTING INSTRUCTIONS

Enable RBAC, share a dashboard with a role.
Share -> copy permalink
Open that link on a private tab (or log out before)
You should be redirected to the login page instead of getting Error {"error": "Error: You don't have access to this dashboard."}

ADDITIONAL INFORMATION

Copies functionality from #30380. I've set noqa: C901 to be able to pass listing while copying the section of code verbatim.
If wanted, this could be refactored into a helper function to reduce complexity

@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented Jun 4, 2026

Code Review Agent Run #8276b3

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 4367390..4367390
    • superset/views/core.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

@dosubot dosubot Bot added authentication Related to authentication authentication:RBAC Related to RBAC dashboard:security:access Related to the security access of the Dashboard labels Jun 4, 2026
@github-actions github-actions Bot added the api Related to the REST API label Jun 4, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 4, 2026

Codecov Report

❌ Patch coverage is 5.55556% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.95%. Comparing base (645aa3b) to head (e3e20ac).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
superset/views/core.py 5.55% 17 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #40769      +/-   ##
==========================================
- Coverage   64.06%   55.95%   -8.11%     
==========================================
  Files        2664     2664              
  Lines      143922   143856      -66     
  Branches    33096    33066      -30     
==========================================
- Hits        92204    80499   -11705     
- Misses      50108    62639   +12531     
+ Partials     1610      718     -892     
Flag Coverage Δ
hive 39.76% <5.55%> (-0.02%) ⬇️
mysql ?
postgres ?
presto 41.35% <5.55%> (-0.02%) ⬇️
python 42.65% <5.55%> (-17.30%) ⬇️
sqlite ?
unit 100.00% <ø> (ø)

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.

Comment thread superset/views/core.py
Comment on lines +865 to +882
dashboard = Dashboard.get(value["dashboardId"])

if not dashboard:
if not get_current_user():
return redirect_to_login()
abort(404)

# Redirect anonymous users to login for unpublished dashboards,
# in the edge case where a dataset has been shared with public
if not get_current_user() and not dashboard.published:
return redirect_to_login()

try:
dashboard.raise_for_access()
except SupersetSecurityException:
if not get_current_user():
return redirect_to_login()
abort(404)
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: This adds a second dashboard fetch and a second permission check even though GetDashboardPermalinkCommand.run() already resolves the dashboard and enforces access via DashboardDAO.get_by_id_or_slug. The duplicate query/check adds avoidable overhead on every permalink hit and can lead to inconsistent outcomes if permissions/state change between checks. Reuse the command's access decision instead of rechecking with another DB round trip. [performance]

Severity Level: Minor 🧹
- ⚠️ Dashboard permalink hits execute two dashboard access checks.
- ⚠️ Extra query overhead negligible relative to request cost.
Steps of Reproduction ✅
1. A user follows a dashboard permalink such as `GET /superset/dashboard/p/<key>/`, which
is handled by `dashboard_permalink` in `superset/views/core.py:848-853`.

2. At `superset/views/core.py:35-40` (hunk lines 855-860), `dashboard_permalink` calls
`GetDashboardPermalinkCommand(key).run()`, whose implementation in
`superset/commands/dashboard/permalink/get.py:42-49` performs `KeyValueDAO.get_value(...)`
and then `DashboardDAO.get_by_id_or_slug(value["dashboardId"])`
(`superset/daos/dashboard.py:135-161`), querying the `Dashboard` table and calling
`dashboard.raise_for_access()` once.

3. If `run()` returns a non-`None` value without raising, `dashboard_permalink` then
executes `dashboard = Dashboard.get(value["dashboardId"])` followed by
`dashboard.raise_for_access()` in the block at `superset/views/core.py:865-882`, issuing a
second database lookup for the same dashboard and repeating the access check within the
same request.

4. This double lookup and double authorization check occur on every successful dashboard
permalink hit, adding an extra DB round-trip and redundant permission evaluation compared
to reusing the command's existing `DashboardDAO.get_by_id_or_slug` access decision.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

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

**Path:** superset/views/core.py
**Line:** 865:882
**Comment:**
	*Performance: This adds a second dashboard fetch and a second permission check even though `GetDashboardPermalinkCommand.run()` already resolves the dashboard and enforces access via `DashboardDAO.get_by_id_or_slug`. The duplicate query/check adds avoidable overhead on every permalink hit and can lead to inconsistent outcomes if permissions/state change between checks. Reuse the command's access decision instead of rechecking with another DB round trip.

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
👍 | 👎

@bito-code-review
Copy link
Copy Markdown
Contributor

The flagged issue is correct. The GetDashboardPermalinkCommand.run() method already resolves the dashboard and performs an access check, making the subsequent Dashboard.get(value["dashboardId"]) and dashboard.raise_for_access() calls redundant.

You can resolve this by reusing the dashboard object if the command returns it, or by ensuring the command's result is sufficient. Since the command already performs the necessary authorization, you can simplify the view logic by removing the redundant database lookup and permission check.

Would you like me to check the rest of the comments on this PR for similar issues?

superset/views/core.py

try:
            value = GetDashboardPermalinkCommand(key).run()
        except (DashboardPermalinkGetFailedError, DashboardAccessDeniedError) as ex:
            if not get_current_user():
                return redirect_to_login()
            return json_error_response(__("Error: %(msg)s", msg=ex.message), status=404)
        if not value:
            if not get_current_user():
                return redirect_to_login()
            return json_error_response(_("permalink state not found"), status=404)

        # The command already validated access and resolved the dashboard.
        # Use the dashboard ID from the command result directly.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes an authentication UX bug where opening a dashboard permalink for a private dashboard while logged out returned a JSON access error instead of redirecting to the login page (aligning permalink behavior with the regular /dashboard/<id>/ route).

Changes:

  • Update dashboard_permalink to redirect unauthenticated users to the login view when access is denied (and in some not-found cases).
  • Add dashboard existence / published checks and an explicit raise_for_access() gate before redirecting to the dashboard route.

Comment thread superset/views/core.py
Comment on lines +865 to 883
dashboard = Dashboard.get(value["dashboardId"])

if not dashboard:
if not get_current_user():
return redirect_to_login()
abort(404)

# Redirect anonymous users to login for unpublished dashboards,
# in the edge case where a dataset has been shared with public
if not get_current_user() and not dashboard.published:
return redirect_to_login()

try:
dashboard.raise_for_access()
except SupersetSecurityException:
if not get_current_user():
return redirect_to_login()
abort(404)
dashboard_id, state = value["dashboardId"], value.get("state", {})
Comment thread superset/views/core.py
Comment on lines 856 to +858
except (DashboardPermalinkGetFailedError, DashboardAccessDeniedError) as ex:
if not get_current_user():
return redirect_to_login()
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 authentication:RBAC Related to RBAC authentication Related to authentication dashboard:security:access Related to the security access of the Dashboard size/S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error accessing a private dashboard without being logged in using a permalink

3 participants