Skip to content

feat(security): force password change on first use (opt-in)#40669

Merged
rusackas merged 5 commits into
masterfrom
feat/force-password-change
Jun 12, 2026
Merged

feat(security): force password change on first use (opt-in)#40669
rusackas merged 5 commits into
masterfrom
feat/force-password-change

Conversation

@rusackas

@rusackas rusackas commented Jun 2, 2026

Copy link
Copy Markdown
Member

SUMMARY

Implements an opt-in force-password-change-on-first-use lifecycle (FINDING-016 / ASVS 6.4.1, CWE-262), for accounts an administrator provisions:

  • Per-user flag password_must_change on the UserAttribute table (Superset's own per-user table — no change to FAB's ab_user), with Alembic migration b7c9d1e2f3a4 (default False, server_default false).
  • superset/security/password_change.py — helpers to set / clear / query the flag, plus a before_request hook that (when ENABLE_FORCE_PASSWORD_CHANGE is on) redirects flagged users to the password-reset page. The hook exempts auth / password-reset / user-info / static / health endpoints to prevent a redirect loop, and is a no-op with zero per-request overhead when disabled (the default).
  • SupersetSecurityManager.reset_password override clears the flag on a successful reset — covering both the self-service reset and the admin "Reset Password" action, which both route through reset_password.
  • New config ENABLE_FORCE_PASSWORD_CHANGE (default False).

Slight trepidation....

  • The before_request redirect runs on the auth path; if the exemption list doesn't match the real FAB endpoint names, it could loop. The list is defensive (matches auth/login/logout/resetmypassword/userinfoedit/static/health substrings, and treats unknown/None endpoints as exempt), but needs validation against a running instance.
  • Needs confirmation that the self-service + admin reset flows clear the flag end-to-end.
  • Out of scope (follow-up): wiring the flag to admin account-creation and secure-random initial-password generation. This PR provides the mechanism + the set_password_must_change() hook for those flows to call.

TESTING INSTRUCTIONS

pytest tests/unit_tests/security/test_password_change.py

Unit tests cover the endpoint-exemption logic (redirect-loop prevention) and the flag query helper. Before merge: flag a user, log in, confirm the redirect to reset, change the password, confirm the flag clears and normal access resumes.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (adds user_attribute.password_must_change; atomic, reversible)
  • Introduces new feature or API (opt-in; default off)
  • Removes existing feature or API

🤖 Generated with Claude Code

@rusackas rusackas added the hold:testing! On hold for testing label Jun 2, 2026
@github-actions github-actions Bot added the risk:db-migration PRs that require a DB migration label Jun 2, 2026
@netlify

netlify Bot commented Jun 2, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 1df7cea
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a2b72f91961a9000801960e
😎 Deploy Preview https://deploy-preview-40669--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.

@codecov

codecov Bot commented Jun 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 32.22222% with 61 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.29%. Comparing base (663b47a) to head (947778c).

Files with missing lines Patch % Lines
superset/security/password_change.py 29.57% 49 Missing and 1 partial ⚠️
superset/security/manager.py 26.66% 11 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #40669      +/-   ##
==========================================
- Coverage   68.67%   64.29%   -4.39%     
==========================================
  Files        1867     2659     +792     
  Lines       78198   144266   +66068     
  Branches    25075    33247    +8172     
==========================================
+ Hits        53701    92749   +39048     
- Misses      24497    49884   +25387     
- Partials        0     1633    +1633     
Flag Coverage Δ
hive 39.45% <32.22%> (?)
mysql 58.18% <32.22%> (?)
postgres 58.24% <32.22%> (?)
presto 41.03% <32.22%> (?)
python 59.71% <32.22%> (-40.29%) ⬇️
sqlite 57.87% <32.22%> (?)
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.

Copilot AI 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.

Pull request overview

Adds an opt-in "force password change on first use" lifecycle: a per-user password_must_change flag on UserAttribute, a before_request hook that redirects flagged users to the password-reset page, and a reset_password override that clears the flag. The feature is gated by a new ENABLE_FORCE_PASSWORD_CHANGE config flag (default off) and is marked as a draft pending end-to-end validation.

Changes:

  • Adds password_must_change boolean column to user_attribute (model + Alembic migration) and helpers to set/clear/query the flag.
  • Registers a before_request enforcement hook with an endpoint-exemption list, and overrides SupersetSecurityManager.reset_password to clear the flag.
  • Introduces ENABLE_FORCE_PASSWORD_CHANGE config and unit tests covering the exemption logic and flag query.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
superset/security/password_change.py New module with flag helpers and the before_request enforcement hook.
superset/security/manager.py Overrides reset_password to clear the forced-change flag.
superset/models/user_attributes.py Adds the password_must_change boolean column.
superset/migrations/versions/2026-06-01_00-00_b7c9d1e2f3a4_add_password_must_change.py Alembic migration adding the new column.
superset/initialization/init.py Wires up the before_request enforcement hook.
superset/config.py Adds the ENABLE_FORCE_PASSWORD_CHANGE config flag (default False).
tests/unit_tests/security/test_password_change.py Unit tests for the exemption helper and password_change_required.

Comment thread superset/security/password_change.py Outdated
Comment thread superset/security/password_change.py Outdated
@rusackas rusackas moved this from Needs Review to Needs Follow-Up Work in Superset Review Help Wanted Jun 2, 2026
@rusackas rusackas added maintain Tracked for ongoing maintenance — keep open and removed hold:testing! On hold for testing labels Jun 9, 2026
@rusackas rusackas marked this pull request as ready for review June 9, 2026 21:30
@dosubot dosubot Bot added authentication Related to authentication change:backend Requires changing the backend install:config Installation - Configuration settings labels Jun 9, 2026
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

⚠️ Translation Regression Detected

A source change in this PR renamed or reworded strings, invalidating existing translations (they are now #, fuzzy) in ar, ca, cs, de, fi, fr, it, ja, ko, lv, mi, nl, pt, ru, sk, sl, th, tr, uk. Please resolve the affected .po files before merging.

Note: intentionally deleting a translatable string is not a regression and is not flagged here — only translations invalidated by a renamed/reworded source string are.

Language Fuzzy before Fuzzy after New
ar 1136 1139 +3
ca 815 818 +3
cs 787 790 +3
de 997 1000 +3
fi 4823 4826 +3
fr 1014 1017 +3
it 1667 1670 +3
ja 322 325 +3
ko 1521 1524 +3
lv 295 298 +3
mi 807 810 +3
nl 1143 1146 +3
pt 1839 1842 +3
ru 293 296 +3
sk 774 777 +3
sl 963 966 +3
th 4823 4826 +3
tr 786 788 +2
uk 293 296 +3

How to fix

1. Install dependencies (if not already set up):

pip install -r superset/translations/requirements.txt
sudo apt-get install gettext   # or: brew install gettext

2. Re-extract strings and sync .po files:

./scripts/translations/babel_update.sh

This rewrites superset/translations/messages.pot from the current source files and merges the changes into every .po file. Strings whose msgid changed will be marked #, fuzzy.

3. Resolve the fuzzy entries in the affected language files (ar, ca, cs, de, fi, fr, it, ja, ko, lv, mi, nl, pt, ru, sk, sl, th, tr, uk):

grep -n '#, fuzzy' superset/translations/<lang>/LC_MESSAGES/messages.po

For each fuzzy entry, either rewrite the msgstr to match the new string and remove the #, fuzzy line, or clear the msgstr to "" if you cannot provide a translation.

4. Commit your changes to the .po files.

@rusackas rusackas moved this from Needs Follow-Up Work to Needs Review in Superset Review Help Wanted Jun 10, 2026
@bito-code-review

bito-code-review Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #7d258e

Actionable Suggestions - 0
Filtered by Review Rules

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

  • superset/migrations/versions/2026-06-01_00-00_b7c9d1e2f3a4_add_password_must_change.py - 1
    • Missing type hints on migration functions · Line 34-47
Review Details
  • Files reviewed - 7 · Commit Range: b52e30a..b52e30a
    • superset/config.py
    • superset/initialization/__init__.py
    • superset/migrations/versions/2026-06-01_00-00_b7c9d1e2f3a4_add_password_must_change.py
    • superset/models/user_attributes.py
    • superset/security/manager.py
    • superset/security/password_change.py
    • tests/unit_tests/security/test_password_change.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

@rusackas rusackas force-pushed the feat/force-password-change branch from b52e30a to 4f67a03 Compare June 10, 2026 00:56
Comment thread superset/security/manager.py Outdated
Comment thread superset/security/password_change.py
Comment thread superset/security/password_change.py Outdated
@rusackas rusackas changed the title feat(security): force password change on first use (opt-in) [draft] feat(security): force password change on first use (opt-in) Jun 10, 2026
@bito-code-review

bito-code-review Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #8166cd

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset/security/password_change.py - 1
    • Missing test coverage · Line 111-116
      The `clear_password_must_change` function (lines 111-116) is a public API function but has no test coverage. The existing test file `tests/unit_tests/security/test_password_change.py` tests `_get_user_attribute`, `password_change_required`, and the enforcement hook, but not this function.
Filtered by Review Rules

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

  • superset/migrations/versions/2026-06-01_00-00_b7c9d1e2f3a4_add_password_must_change.py - 1
    • Invalid server_default for Boolean column · Line 41-41
Review Details
  • Files reviewed - 8 · Commit Range: 4f67a03..4d1427f
    • superset/config.py
    • superset/initialization/__init__.py
    • superset/migrations/versions/2026-06-01_00-00_b7c9d1e2f3a4_add_password_must_change.py
    • superset/models/user_attributes.py
    • superset/security/manager.py
    • superset/security/password_change.py
    • tests/unit_tests/security/manager_test.py
    • tests/unit_tests/security/test_password_change.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

@eschutho eschutho moved this from Needs Review to In Review in Superset Review Help Wanted Jun 10, 2026
@sadpandajoe

Copy link
Copy Markdown
Member

LGTM but #40676 also has a migration, so may need to rebase and update the down revision on whichever pr is merged second.

claude and others added 3 commits June 11, 2026 19:44
Add an opt-in "must change password" lifecycle for accounts (typically created
by an administrator):

- New per-user flag password_must_change on the UserAttribute table (+ Alembic
  migration b7c9d1e2f3a4, default False).
- superset/security/password_change.py: helpers to set/clear/query the flag and
  a before_request hook that, when ENABLE_FORCE_PASSWORD_CHANGE is enabled,
  redirects flagged users to the password-reset page. The hook exempts auth /
  password-reset / static / health endpoints to avoid a redirect loop, and is a
  no-op (zero overhead) when the feature is disabled (the default).
- SupersetSecurityManager.reset_password override clears the flag on a
  successful reset (covers both self-service and admin reset, which both route
  through reset_password).
- New config ENABLE_FORCE_PASSWORD_CHANGE (default False).

Unit tests cover the endpoint-exemption logic (redirect-loop prevention) and
the flag query helper.

DRAFT: behavior-sensitive request middleware on the auth path. Needs end-to-end
validation that the exemption list matches the actual FAB endpoint names (no
redirect loop) and that the reset flow clears the flag, before merge. Wiring the
flag to admin-create + secure-random initial passwords is a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Match exempt view classes exactly instead of substring matching so
  unrelated endpoints sharing a substring are not exempted from
  force-password-change enforcement.
- Point the migration down_revision at the current alembic head.
- Use @transaction() instead of manual db.session.commit().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address three codeant-ai review findings on the force-password-change
feature:

- Admin-initiated resets no longer clear the must-change flag. Only a
  self-service reset (acting user == target user) clears it, so an admin
  setting a temporary password preserves the change-at-next-login
  requirement.
- _get_user_attribute fetches deterministically via order_by(id).first()
  instead of one_or_none(), so duplicate user_attribute rows (the table
  has no uniqueness on user_id) can't raise MultipleResultsFound / 500.
- The enforcement redirect fallback now targets an exempt route (logout)
  and returns a 503 if no exempt target resolves, instead of redirecting
  to the non-exempt index and trapping flagged users in a 302 loop.

Adds unit tests pinning each corrected behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rusackas rusackas force-pushed the feat/force-password-change branch from 4d1427f to 1df7cea Compare June 12, 2026 02:46
@rusackas

Copy link
Copy Markdown
Member Author

Thanks @sadpandajoe! Already rebased onto current master and repointed down_revision to f7a1c93e0b21 (the sessions-invalidated migration), so #40676 and this one are on separate heads off the same parent rather than colliding. Will merge once CI is green.

Comment thread superset/security/password_change.py
Comment thread tests/unit_tests/security/test_password_change.py Outdated
Comment thread tests/unit_tests/security/test_password_change.py
Comment thread tests/unit_tests/security/test_password_change.py
Comment thread tests/unit_tests/security/test_password_change.py
Comment thread tests/unit_tests/security/test_password_change.py Outdated
Comment thread superset/security/password_change.py
Comment thread superset/security/password_change.py Outdated
rusackas and others added 2 commits June 11, 2026 21:06
Master gained two sibling alembic heads (c8d2e3f4a5b6 and f7a1c93e0b21,
both revising 31dae2559c05), so any branch adding a migration on top of
just one of them fails 'superset db upgrade' with multiple heads. Make
this PR's migration a merge point over both heads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Upsert-with-retry in set_password_must_change: the unique constraint on
  user_attribute.user_id means a concurrent insert can win between the read
  and the flush; catch IntegrityError in a nested transaction and update the
  winner's row (mirrors the session-invalidation upsert).
- Derive the logout fallback from the registered auth view so non-DB auth
  backends (LDAP/OAuth/OID/remote-user) get a working exempt fallback,
  guarded so a non-exempt custom auth view can never become a loop target.
- Refresh stale comments that predate the user_id unique constraint.
- Typing/docstring nits: annotate migration upgrade/downgrade, test params
  and fixtures; add docstrings to the before-request hook and tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rusackas

Copy link
Copy Markdown
Member Author

@sadpandajoe you called it... #40676 and #40695 both landed as siblings of 31dae2559c05, so master ended up with two heads and every backend job here failed on Multiple head revisions. Made this PR's migration a merge revision over both.

@rusackas rusackas merged commit 814b72c into master Jun 12, 2026
59 checks passed
@rusackas rusackas deleted the feat/force-password-change branch June 12, 2026 05:23
@github-project-automation github-project-automation Bot moved this from In Review to Approved and/or Merged in Superset Review Help Wanted Jun 12, 2026
@bito-code-review

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped – PR Already Merged

Bito scheduled an automatic review for this pull request, but the review was skipped because this PR was merged before the review could be run.
No action is needed if you didn't intend to review it. To get a review, you can type /review in a comment and save it

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

Labels

authentication Related to authentication change:backend Requires changing the backend install:config Installation - Configuration settings maintain Tracked for ongoing maintenance — keep open preset-io risk:db-migration PRs that require a DB migration size/XL

Projects

Status: Approved and/or Merged

Development

Successfully merging this pull request may close these issues.

5 participants