Dhruv-kys opened a new pull request, #70797:
URL: https://github.com/apache/airflow/pull/70797

   FAB's Security pages generated Add/Edit/Show links without the `/auth` 
blueprint
   prefix when Airflow runs behind a reverse proxy on a URL subpath.
   
   closes: #70389
   
   ## Problem
   
   With `[fab] enable_proxy_fix = True` and the proxy sending 
`X-Forwarded-Prefix`,
   links on `/auth/roles/list/` and `/auth/users/list/` point at
   `/<subpath>/roles/add` instead of `/<subpath>/auth/roles/add`. The broken 
link
   resolves to the React SPA shell, which has no client-side route for
   `roles`/`users` and redirects to the Dashboard — so the buttons appear to do
   nothing, with no visible error.
   
   ## Cause
   
   The Flask app is mounted at `/` inside a FastAPI sub-app
   (`fab_auth_manager.py:256`), which is itself mounted at `/auth`
   (`api_fastapi/app.py:206`), so Starlette passes `SCRIPT_NAME=/auth` into the 
WSGI
   environ. Werkzeug's `ProxyFix` then *assigns* `X-Forwarded-Prefix` to
   `SCRIPT_NAME` rather than prepending to it:
   
   ```python
   if x_prefix:
       environ["SCRIPT_NAME"] = x_prefix
   ```
   
   That discards the mount path. Since `url_for` builds every URL as
   `SCRIPT_NAME + rule`, all FAB-generated links lose `/auth`. The list pages
   themselves keep the prefix because those URLs come from the React shell's
   configuration rather than from `url_for`, which is why the bug looks 
selective.
   
   ## Fix
   
   Wrap `ProxyFix` in a pair of small WSGI middlewares: the outer one stashes 
the
   mount path and clears `SCRIPT_NAME` so `ProxyFix` sees an empty slot, and the
   inner one appends the mount path back once the forwarded prefix has been
   applied. The result is always `forwarded_prefix + mount_path`.
   
   An earlier approach re-appended the mount path only when `SCRIPT_NAME` 
differed
   from its original value. That is wrong when the proxy subpath happens to 
equal
   the mount path (`X-Forwarded-Prefix: /auth`): the two values match, the guard
   skips, and the prefix is silently dropped. The stash/restore pair needs no 
such
   comparison; that case is covered by the
   `subpath-proxy-prefix-matching-mount-path` test.
   
   ## Testing
   
   New 
`providers/fab/tests/unit/fab/www/extensions/test_init_wsgi_middlewares.py`
   covers mounted/unmounted x forwarded-prefix/no-prefix, the equal-prefix edge
   case, and proxy-fix disabled. Two cases fail without the fix
   (`mounted-behind-subpath-proxy` and 
`subpath-proxy-prefix-matching-mount-path`);
   the other four pass either way and act as controls against a fix that
   over-applies the prefix.
   
   ```
   uv run --project providers/fab pytest providers/fab/tests/unit/fab/www/
   # 67 passed
   
   uv run --project airflow-core pytest 
airflow-core/tests/unit/always/test_project_structure.py
   # 10 passed, 1 xfailed
   ```
   
   This also removes the module from the `OVERLOOKED_TESTS` allow-list in
   `airflow-core/tests/unit/always/test_project_structure.py`, which the 
project is
   driving to zero.
   
   ---
   
   ##### Was generative AI tooling used to co-author this PR?
   
   - [X] Yes — Claude Code (Opus 5)
   
   Generated-by: Claude Code (Opus 5) following [the 
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
   


-- 
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]

Reply via email to