eschutho opened a new pull request, #44213:
URL: https://github.com/apache/superset/pull/44213

   ### SUMMARY
   
   `CurrentUserRestApi`'s three `/api/v1/me/` endpoints — `get_me`, 
`get_my_roles`, and `update_me` — resolve `g.user` (a flask-login lazy proxy) 
inside their method bodies. When flask-login lazily resolves that proxy for a 
**validly-authenticated** JWT whose bearer is a real workspace member with **no 
assigned role** (Preset Manager's "no access" sentinel), it raises 
`UserLookupError("No access")`. A missing/invalid authorization context 
similarly raises `NoAuthorizationError`.
   
   Neither exception was caught anywhere in the call chain. It propagated all 
the way to flask_appbuilder's `@safe` decorator, whose bare `except Exception: 
log.exception(e); return self.response_500(...)` logs a **full ERROR 
traceback** and returns a generic **500** — instead of the clean **401** these 
endpoints are documented to return for unauthenticated/unauthorized callers.
   
   **Sentry:** 
[SUPERSET-PYTHON-NMA](https://preset-inc.sentry.io/issues/5588396277/) — 
`UserLookupError: No access`, culprit `CurrentUserRestApi.get_me`. **10,471 
events / 31 users** (firstSeen 2024-07-09, still firing).
   
   **Root cause (two overlapping gaps):**
   1. **Regression from #36410** (merged 2025-12-11): before that PR, all three 
methods carried manual `try/except NoAuthorizationError: return 
self.response_401()` guards around `g.user` access. #36410 removed them, on the 
assumption that `@protect()` fully covers auth failures. It does not — 
flask_appbuilder's `protect()` performs no exception handling of its own around 
the lazy `g.user`/`current_user` resolution that happens later, inside the body.
   2. **Always-missing `UserLookupError` coverage:** even the old guards only 
caught `NoAuthorizationError`. `UserLookupError` (the "member with no role" 
path, which Preset treats as the *expected, routine* outcome for those users — 
not a sync gap) was never handled here at all.
   
   ### FIX
   
   Add explicit handling around the `g.user` access in all three 
`CurrentUserRestApi` methods, catching both `NoAuthorizationError` and 
`UserLookupError` (both from `flask_jwt_extended.exceptions`) and returning 
`self.response_401()`. This mirrors the established precedent already in this 
codebase — `superset/views/base.py`'s `api` decorator — which logs auth-outcome 
exceptions at **WARNING** (not ERROR/`exc_info`-heavy stack spam) before 
returning 401, reserving ERROR+500 for genuinely unexpected failures.
   
   The change is deliberately surgical:
   - `@protect()`/flask_appbuilder is third-party and untouched.
   - Other endpoints in the file (e.g. `UserRestApi`/avatar) that were never 
part of this bug are untouched.
   - `update_me` already had a `try/except ValidationError` block, so the new 
`except` clause was added alongside it rather than nesting a second `try`.
   
   ### TRADEOFFS
   
   **None.** This strictly repairs a broken-in-a-worse-way-than-intended auth 
response: a request that should always have been a `401` was instead emitting a 
`500` plus a noisy ERROR traceback on every occurrence. No 
legitimate-user-facing behavior changes — successful requests still return 
`200` with identical payloads, and genuinely unexpected exceptions still fall 
through to `@safe`'s ERROR+500 handler unchanged. The only observable 
differences are (a) the correct `401` status for the affected auth outcomes and 
(b) WARNING-level (instead of ERROR-level) logging for them.
   
   ### FOLLOW-UPS
   
   None expected.
   
   ### TESTING INSTRUCTIONS
   
   New unit tests: `tests/unit_tests/views/test_current_user_auth_errors.py`.
   
   They drive the real endpoints via the test client (with `@protect()` 
satisfied through the `full_api_access` fixture) and patch the module-level `g` 
so that resolving `g.user` inside each body raises the target exception — 
exactly as flask-login does in production — then assert the response is `401`.
   
   Coverage: `get_me`, `get_my_roles`, and `update_me`, each against both 
`UserLookupError` and `NoAuthorizationError` (6 cases).
   
   **Non-vacuous verification (stash-the-fix / keep-the-tests):**
   
   ```
   # WITH the fix:
   $ pytest tests/unit_tests/views/test_current_user_auth_errors.py -q
   ......                                                                   
[100%]
   6 passed in 0.96s
   
   # git stash push -- superset/views/users/api.py   (revert only the fix, keep 
the tests)
   $ pytest tests/unit_tests/views/test_current_user_auth_errors.py -q
   ...
       return self.response(200, result=user_response_schema.dump(g.user))
                                                                  ^^^^^^
   flask_jwt_extended.exceptions.NoAuthorizationError: Missing Authorization 
Header
   6 failed in 1.18s
   ```
   
   The pre-existing `tests/unit_tests/views/test_current_user_api.py` (4 tests) 
continues to pass. `pre-commit run --files ...` (ruff, ruff-format, mypy, 
pylint) passes on the changed files.
   
   ### ADDITIONAL INFORMATION
   - [ ] Has associated issue:
   - [ ] Required feature flags:
   - [ ] Changes UI
   - [ ] Includes DB Migration (follow approval process in 
[SIP-59](https://github.com/apache/superset/issues/13351))
     - [ ] 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
   
   ---
   
   Shortcut: [SC-120417](https://app.shortcut.com/preset/story/120417)
   Fixes SUPERSET-PYTHON-NMA
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to