2020from datetime import UTC , datetime
2121from unittest import mock
2222
23- import httpx
2423import httpx2
2524import jwt
2625import pytest
@@ -125,8 +124,8 @@ def verifier(client, exec_app, signing_key, identity_algorithm, monkeypatch):
125124 }
126125 else :
127126 public_key = key_to_jwk_dict (signing_key , kid = "key-1" )
128- jwks_client = httpx .AsyncClient (
129- transport = httpx .MockTransport (lambda request : httpx .Response (200 , json = {"keys" : [public_key ]}))
127+ jwks_client = httpx2 .AsyncClient (
128+ transport = httpx2 .MockTransport (lambda request : httpx2 .Response (200 , json = {"keys" : [public_key ]}))
130129 )
131130 verifier = svcs .Container (exec_app .state .svcs_registry ).get (DagProcessorIdentityVerifier )
132131 monkeypatch .setattr (verifier .validator .jwks , "client" , jwks_client )
@@ -305,7 +304,7 @@ def test_rotated_signing_key_refreshes_after_cooldown(
305304
306305 async def get_keys (url ):
307306 requests .append (url )
308- return httpx .Response (200 , json = {"keys" : keys }, request = httpx .Request ("GET" , url ))
307+ return httpx2 .Response (200 , json = {"keys" : keys }, request = httpx2 .Request ("GET" , url ))
309308
310309 monkeypatch .setattr (verifier .validator .jwks .client , "get" , get_keys )
311310 original = decode (exchange (client , identity_token ()))
@@ -371,15 +370,15 @@ async def expire(token):
371370def test_jwks_outage_is_retryable (client , verifier , identity_token , monkeypatch , failure ):
372371 async def unavailable (url ):
373372 if failure == "connection" :
374- raise httpx .ConnectError ("offline" )
375- request = httpx .Request ("GET" , url )
373+ raise httpx2 .ConnectError ("offline" )
374+ request = httpx2 .Request ("GET" , url )
376375 if failure == "503" :
377- return httpx .Response (503 , request = request )
376+ return httpx2 .Response (503 , request = request )
378377 if failure == "invalid-json" :
379- return httpx .Response (200 , content = b"unavailable" , request = request )
378+ return httpx2 .Response (200 , content = b"unavailable" , request = request )
380379 if failure == "json-array" :
381- return httpx .Response (200 , json = [{"kty" : "OKP" }], request = request )
382- return httpx .Response (200 , json = {"keys" : []}, request = request )
380+ return httpx2 .Response (200 , json = [{"kty" : "OKP" }], request = request )
381+ return httpx2 .Response (200 , json = {"keys" : [None ]}, request = request )
383382
384383 monkeypatch .setattr (verifier .jwks .client , "get" , unavailable )
385384 response = exchange (client , identity_token ())
@@ -388,12 +387,32 @@ async def unavailable(url):
388387 assert response .headers ["Retry-After" ] == "10"
389388
390389
390+ @pytest .mark .parametrize ("cached" , [False , True ])
391+ @mock .patch ("airflow.api_fastapi.auth.tokens.time.monotonic" , autospec = True , return_value = 100 )
392+ def test_empty_jwks_rejects_identities_with_or_without_cached_keys (
393+ clock , client , verifier , identity_token , monkeypatch , cached
394+ ):
395+ if cached :
396+ assert exchange (client , identity_token ()).status_code == 200
397+ clock .return_value = 3701
398+
399+ async def get_keys (url ):
400+ return httpx2 .Response (200 , json = {"keys" : []}, request = httpx2 .Request ("GET" , url ))
401+
402+ monkeypatch .setattr (verifier .jwks .client , "get" , get_keys )
403+ response = exchange (client , identity_token ())
404+ assert response .status_code == 401
405+ assert response .json () == {"detail" : "Invalid workload identity" }
406+ assert response .headers ["WWW-Authenticate" ] == "Bearer"
407+ assert "Retry-After" not in response .headers
408+
409+
391410@mock .patch ("airflow.api_fastapi.auth.tokens.time.monotonic" , autospec = True , return_value = 100 )
392411def test_jwks_outage_preserves_cached_key_validation (clock , client , verifier , identity_token , monkeypatch ):
393412 assert exchange (client , identity_token ()).status_code == 200
394413
395414 async def unavailable (url ):
396- raise httpx .ConnectError ("offline" )
415+ raise httpx2 .ConnectError ("offline" )
397416
398417 monkeypatch .setattr (verifier .jwks .client , "get" , unavailable )
399418 clock .return_value = 3701
@@ -408,10 +427,10 @@ def test_registration_recovers_from_jwks_outage(clock, make_processor, verifier,
408427
409428 async def get_keys (url ):
410429 attempts .append (url )
411- return httpx .Response (
430+ return httpx2 .Response (
412431 503 if len (attempts ) == 1 else 200 ,
413432 json = {"keys" : [key_to_jwk_dict (signing_key , kid = "key-1" )]},
414- request = httpx .Request ("GET" , url ),
433+ request = httpx2 .Request ("GET" , url ),
415434 )
416435
417436 waits = []
0 commit comments