potiuk opened a new pull request, #73698: URL: https://github.com/apache/airflow/pull/73698
The AWS auth manager's SAML login flow did not bind the authentication response to the browser that started it. `login()` discarded the AuthnRequest id that `saml_auth.login()` generates, and `login_callback()` called `process_response()` with no `request_id`. python3-saml only validates `InResponseTo` when it is given that id, so the check was skipped and any assertion the IdP had signed was accepted. A signed assertion authenticates *the identity in the response*; it does not establish *which browser asked*. An assertion issued for one login could therefore be completed in a different browser that never started a login, signing that browser in as the assertion's subject rather than as the person using it. `RelayState` only selected the return mode and arrived in the same form, so the response could choose that too. With this change the SAML response is validated against the AuthnRequest id of the login that started it. ### The change Each login gets a nonce that travels to the IdP in `RelayState` and names, on the way back, which of this browser's pending logins the response answers. The pending set lives in a short-lived signed cookie (`HttpOnly`, `SameSite=Lax`, `Secure` behind TLS, 10 minutes, one entry per login). At the callback the response must match one of those entries, and `InResponseTo` must match that entry's AuthnRequest id. The entry is consumed on use — on both the redirect and the token paths — so a request id cannot answer a second response, and logins started in other tabs are left alone. ### IdP-initiated SSO Binding the response to an SP-initiated request means a SAML POST that did not start at this browser's `/login` is refused, which includes clicking the Airflow tile in the AWS Identity Center access portal. Per the discussion on #72647, that flow is kept available behind an opt-in rather than broken — `[aws_auth_manager] allow_idp_initiated_login`, defaulting to `False`. With it enabled the assertion must still be signed by the configured IdP, and a response carrying `InResponseTo` is still refused: an unsolicited assertion answers no request, so one naming a request is a solicited assertion being replayed. What it gives up is the guarantee that the browser receiving the response is the one that asked for it. Documented in `providers/amazon/docs/auth-manager/setup/identity-center.rst`. ### Test collection fix `TestLoginRouter` was never collected — by anyone, including CI. `mock_plugin_manager` is a `contextmanager`, and a `ContextDecorator` applied to a class replaces the class with a function, so pytest found nothing and reported success. It is now an autouse fixture, which ran the module's tests for the first time and surfaced two assertions that had never executed: a missing `[api] base_url` in the test config, and a `SameSite` check comparing against the wrong case. There are 27 tests in the file now. `check_contextmanager_class_decorators` already guards this failure mode but did not list `mock_plugin_manager`; it does now, with tests in `scripts/tests/ci/prek/test_check_contextmanager_class_decorators.py`. This file was the only one in the repo affected. Supersedes #72647 (same change, squashed into one commit). --- ##### Was generative AI tooling used to co-author this PR? - [X] Yes — Claude Code (Claude Opus 5) Generated-by: Claude Opus 5 following [the guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions) 🤖 Generated with [Claude Code](https://claude.com/claude-code) -- 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]
