You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by "potiuk (via GitHub)" <gi...@apache.org> on 2026/08/28 17:57:08 UTC

[PR] Resolve the caller from the explicit credential, not the session cookie [airflow]

potiuk opened a new pull request, #72225:
URL: https://github.com/apache/airflow/pull/72225

   `get_user()` codes an explicit precedence — bearer, then OAuth2, then the
   session cookie:
   
   ```python
   if bearer_credentials and bearer_credentials.scheme.lower() == "bearer":
       token_str = bearer_credentials.credentials
   elif oauth_token:
       token_str = oauth_token
   else:
       token_str = request.cookies.get(COOKIE_NAME_JWT_TOKEN)
   ```
   
   That block was never reached when a session cookie was present. `JWTRefreshMiddleware`
   runs first, resolves a user from the `_token` cookie alone, and stamps it on
   `request.state` together with the trust sentinel; `get_user()` returned that cached
   user up front, before looking at either explicit credential. The effective order on
   every core-API route was therefore **cookie over bearer** — the inverse of what the
   function reads as doing.
   
   So a request carrying both a cookie and an explicit `Authorization: Bearer` token ran
   as the cookie's principal. The token the client deliberately presented was ignored,
   and the request was recorded in the audit log under the wrong identity.
   
   ## The change
   
   The cached user is honoured only when the request carries no explicit credential —
   which is the case it exists for: a browser session whose token the middleware has just
   refreshed. When a bearer or OAuth2 token is present, that token is resolved instead.
   
   The trust-sentinel check is unchanged and still guards the cached-user path; it has
   simply moved inside the no-explicit-credential branch.
   
   ## Behaviour
   
   | Request carries | Resolved as |
   |---|---|
   | cookie only | cookie principal (refreshed by the middleware, as before) |
   | bearer only | bearer principal (as before) |
   | OAuth2 only | OAuth2 principal (as before) |
   | cookie **and** bearer | bearer principal (**changed** — was the cookie) |
   | cookie **and** OAuth2 | OAuth2 principal (**changed** — was the cookie) |
   
   Only the two mixed-credential rows change. A client that sends one credential is
   unaffected, and cookie-only browser sessions keep the refresh behaviour intact.
   
   ## Tests
   
   `test_get_user_explicit_credential_beats_cookie_user`, parametrised over bearer and
   OAuth2: a trusted cookie-derived user is stamped on `request.state` *and* an explicit
   credential is supplied; the explicit one must win. Both fail if the source change is
   reverted.
   
   The existing `test_get_user_with_trusted_request_state` still passes unmodified — it
   supplies no explicit credential, so it exercises the path that was deliberately kept.
   
   140 passed across `core_api/test_security.py` and `auth/middlewares/`; ruff clean.
   
   🤖 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: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Resolve the caller from the explicit credential, not the session cookie [airflow]

Posted by "pierrejeambrun (via GitHub)" <gi...@apache.org>.
pierrejeambrun commented on code in PR #72225:
URL: https://github.com/apache/airflow/pull/72225#discussion_r3934662153


##########
airflow-core/newsfragments/72225.significant.rst:
##########


Review Comment:
   Newsfragment doesn't mention: invalid bearer + valid cookie now fails loud where it used to silently fall back to cookie. Fine but worth a line.



##########
airflow-core/src/airflow/api_fastapi/core_api/security.py:
##########
@@ -147,22 +147,26 @@ async def get_user(
     oauth_token: str | None = Depends(oauth2_scheme),
     bearer_credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
 ) -> BaseUser:
-    # A user might have been already built by a trusted in-tree middleware (currently
-    # only `JWTRefreshMiddleware`); if so, it is stored in `request.state.user` AND
-    # `request.state.user_authenticated_via` is set to the trust sentinel above.
-    # Honour the cached user only when both are present, so a stray `state.user`
-    # assignment from unrelated middleware can't bypass JWT validation.
-    user: BaseUser | None = getattr(request.state, "user", None)
-    trust_marker = getattr(request.state, "user_authenticated_via", None)
-    if user and trust_marker is USER_INJECTED_BY_TRUSTED_MIDDLEWARE:
-        return user
-
+    # An explicitly supplied credential always wins over the ambient session cookie.
     token_str: str | None
     if bearer_credentials and bearer_credentials.scheme.lower() == "bearer":
         token_str = bearer_credentials.credentials
     elif oauth_token:
         token_str = oauth_token
     else:
+        token_str = None

Review Comment:
   This `token_str = None` looks weird.
   
   Early returns eveywhere read better, maybe:
   ```python
     async def get_user(request, oauth_token, bearer_credentials) -> BaseUser:
         if bearer_credentials and bearer_credentials.scheme.lower() == "bearer":
             return await resolve_user_from_token(bearer_credentials.credentials)
         if oauth_token:
             return await resolve_user_from_token(oauth_token)
     
         # No explicit credential — cached middleware user, else cookie.
         user = getattr(request.state, "user", None)
         trust_marker = getattr(request.state, "user_authenticated_via", None)
         if user and trust_marker is USER_INJECTED_BY_TRUSTED_MIDDLEWARE:
             return user
         return await resolve_user_from_token(request.cookies.get(COOKIE_NAME_JWT_TOKEN))
   ```



-- 
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: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Resolve the caller from the explicit credential, not the session cookie [airflow]

Posted by "potiuk (via GitHub)" <gi...@apache.org>.
potiuk merged PR #72225:
URL: https://github.com/apache/airflow/pull/72225


-- 
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: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org