Skip to content

fix(reports): stamp login time on machine-auth sessions - #45876

Draft
sadpandajoe wants to merge 2 commits into
masterfrom
fix-report-machine-auth-login-stamp
Draft

sadpandajoe wants to merge 2 commits into
masterfrom
fix-report-machine-auth-login-stamp

Conversation

@sadpandajoe

Copy link
Copy Markdown
Member

SUMMARY

Scheduled CSV and XLSX chart reports fail on every run with Chart data request failed: category=http status=401 once the report owner has had a password change or reset (or any other session invalidation). The chart itself loads fine in the UI, and the failure happens before any query runs.

Root Cause

MachineAuthProvider.get_auth_cookies logs the report owner in with a bare login_user(user) inside a throwaway request context and replays the resulting session cookie against /api/v1/chart/data. Unlike a normal login, which stamps session["_login_at"] through on_user_login, that session carries only the Flask-Login keys.

enforce_session_validity runs before every request and compares _login_at with the user's sessions_invalidated_at epoch. is_session_invalidated(None, epoch) deliberately fails closed, so once the user has an epoch the hook logs the machine-minted session out, the request continues anonymously, and @protect() falls through to the JWT check and answers 401 Missing Authorization Header. The report code then surfaces that as the generic chart-data HTTP error.

The line itself is old, and the missing stamp only matters since the session-invalidation hook (#40695) and the change that started stamping the epoch on password change and reset for active users (#42934, and the later reset and recovery flows). Users who never changed their password have no epoch and are unaffected, which is why reports worked until a routine password reset.

The fix stamps the login time right after a successful login_user(), the same way a normal login does, so a session minted after the epoch is valid. The stamp is only applied when login_user() succeeds: an inactive user is still refused and get_auth_cookies still returns {}, which is the empty-cookie case the report code reports as missing authentication cookies. The hook, and its fail-closed handling of sessions that predate it, are unchanged: a session whose login time is before the epoch is still rejected.

Not addressed here

  • A session minted within the same wall-clock second as the password change can still be rejected, because the epoch is rounded up to the next whole second. This also affects regular logins and clears on the next run.
  • A custom MACHINE_AUTH_PROVIDER_CLASS that calls login_user itself needs the same stamp.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

UI QA: not-required (backend-only change to how a server-side session is minted; no user-visible UI surface). The regression test is the evidence.

Before (the regression test run without the fix), tests/unit_tests/utils/machine_auth_test.py:

FAILED test_machine_auth_cookie_survives_epoch
AssertionError: machine-minted session was rejected by enforce_session_validity (status 401); the session carries no '_login_at', so it is treated as older than the epoch
assert 401 == 200
1 failed, 3 passed

After (this PR): 5 passed.

TESTING INSTRUCTIONS

Automated:

pytest tests/unit_tests/utils/machine_auth_test.py

The new tests mint a session with MachineAuthProvider.get_auth_cookies, replay the cookie through a real request that passes enforce_session_validity, and assert the result:

  • test_machine_auth_cookie_survives_epoch fails without the fix (401) and passes with it. It stamps the epoch in the past through the real invalidate_sessions_for_user.
  • Controls: no epoch (200), a session stamped after the epoch (200), a session stamped before the epoch (401, invalidation of real sessions is unchanged), and an inactive user ({}, no cookie).

Manual:

  1. Enable alerts and reports with a working SMTP server, a Celery worker and beat.
  2. As an admin, change a user's password (Settings > List Users > edit), or run superset fab reset-password.
  3. As that user, create a CSV report on a chart that has a saved query, scheduled every minute.
  4. Before this change the run fails with Chart data request failed: category=http status=401; with it the email carries the CSV and the run succeeds.

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

sadpandajoe and others added 2 commits October 30, 2026 05:28
Add a regression test that mints a report-execution session with
MachineAuthProvider.get_auth_cookies for a user whose session-invalidation
epoch is set (as after a password change), replays the cookie against a
real app request, and asserts the request stays authenticated.

The session has no login time, so enforce_session_validity treats it as
older than the epoch and the request is rejected with a 401. The test
fails until machine-minted sessions carry a login time.

Controls cover the same cookie with no epoch, and sessions with a login
time after and before the epoch.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
MachineAuthProvider.get_auth_cookies logs the report owner in with a bare
login_user(), so the minted session has no _login_at. Once a user has a
session-invalidation epoch (any password change or reset), the
enforce_session_validity hook treats such a session as older than the epoch,
logs it out, and the replayed request is anonymous. CSV and XLSX report
chart-data fetches then fail with a 401 on every run.

Stamp the login time the same way a normal login does, so a session minted
after the epoch is valid.
@codecov

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.71%. Comparing base (4e6d03b) to head (c75d96f).

Additional details and impacted files
@@             Coverage Diff             @@
##           master   #45876       +/-   ##
===========================================
+ Coverage   65.62%   84.71%   +19.09%     
===========================================
  Files        3069     3069               
  Lines      205661   205663        +2     
  Branches    48336    48337        +1     
===========================================
+ Hits       134955   174220    +39265     
+ Misses      67625    28150    -39475     
- Partials     3081     3293      +212     
Flag Coverage Δ
hive 34.51% <33.33%> (+<0.01%) ⬆️
mysql 52.58% <66.66%> (-0.01%) ⬇️
postgres 52.59% <66.66%> (-0.01%) ⬇️
presto 36.24% <33.33%> (+<0.01%) ⬆️
python 87.75% <100.00%> (+34.89%) ⬆️
sqlite 52.35% <66.66%> (-0.01%) ⬇️
unit 82.49% <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.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant