GayathriSrividya commented on code in PR #68499:
URL: https://github.com/apache/airflow/pull/68499#discussion_r3411054737
##########
airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_router.py:
##########
@@ -61,9 +120,49 @@ def test_expiring_token_is_reissued(
lifespan.registry.register_value(JWTValidator, auth)
# In order to test this we need any endpoint to hit. The easiest one to
use is variable get
- response = client.get("/execution/variables/key1",
headers={"Authorization": "Bearer dummy"})
+ response = jwt_bearer_client.get("/execution/variables/key1",
headers={"Authorization": "Bearer dummy"})
if expect_refreshed_token:
assert "Refreshed-API-Token" in response.headers
else:
assert "Refreshed-API-Token" not in response.headers
+ # avalidated_claims must be called exactly once — by JWTBearer only, not
by the middleware.
+ auth.avalidated_claims.assert_awaited_once_with("dummy", {})
+
+
[email protected]_test
+def test_token_expiring_mid_request_is_reissued_without_revalidation(
+ jwt_bearer_client, exec_app: FastAPI, time_machine
+):
+ """Middleware reissues from cached JWTBearer claims without re-validating
the token.
+
+ Regression test for the TOCTOU race in JWTReissueMiddleware: a heartbeat
arrives with a
+ token that has ~0s left, JWTBearer validates it (still technically valid
at that moment),
+ the request starts, and the middleware runs. In the old code the
middleware would call
+ avalidated_claims a second time and get ExpiredSignatureError — no
Refreshed-API-Token
+ header would be set, and the task would die on the next heartbeat.
+
+ With the fix the middleware reads claims from request.scope (set by
JWTBearer) instead of
+ calling avalidated_claims again, so it still issues a fresh token even
when the original
+ has since expired.
+ """
+ moment = 1743451846
+ auth = AsyncMock(spec=JWTValidator)
+ auth.avalidated_claims.return_value = {
+ "sub": "edb09971-4e0e-4221-ad3f-800852d38085",
+ "iat": moment,
+ "exp": moment + 600,
+ }
+
+ # Move time to 1 second past the token's expiry. JWTBearer already
accepted the token
+ # (mocked); the middleware must still issue a refresh using the cached
claims rather than
+ # silently dropping it.
+ time_machine.move_to(moment + 601, tick=False)
+
+ lifespan.registry.register_value(JWTValidator, auth)
+
+ response = jwt_bearer_client.get("/execution/variables/key1",
headers={"Authorization": "Bearer dummy"})
+
+ assert "Refreshed-API-Token" in response.headers
Review Comment:
@ashb
- Fixed! The test now properly validates the TOCTOU scenario:
**Test:** test_router.py
- Token expires mid-request (line 157: `time_machine.move_to(moment + 601)`)
- Middleware still refreshes (line 163: `assert "Refreshed-API-Token" in
response.headers`)
- No re-validation happens (line 164: `assert_awaited_once_with` proves
`avalidated_claims` called only once)
**Fix:** app.py
- Middleware reads from `request.scope` (cached by JWTBearer) instead of
calling `avalidated_claims` again
- Guarantees refresh even if token expired between request start and
middleware execution
Let me know if any other method is needed to address the issue.
--
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]