@@ -342,3 +342,125 @@ def test_options_requests_with_cors_headers(source_api_server):
342342    assert  (
343343        response .status_code  ==  200 
344344    ), "OPTIONS request with CORS headers should succeed" 
345+ 
346+ 
347+ @pytest .mark .parametrize ( 
348+     "token_audiences,allowed_audiences,expected_status" , 
349+     [ 
350+         # Single audience scenarios  
351+         (["stac-api" ], "stac-api" , 200 ), 
352+         (["stac-api" ], "different-api" , 401 ), 
353+         (["stac-api" ], "stac-api,other-api" , 200 ), 
354+         # Multiple audiences in token  
355+         (["stac-api" , "other-api" ], "stac-api" , 200 ), 
356+         (["stac-api" , "other-api" ], "other-api" , 200 ), 
357+         (["stac-api" , "other-api" ], "different-api" , 401 ), 
358+         (["stac-api" , "other-api" ], "stac-api, other-api,third-api" , 200 ), 
359+         # No audience in token  
360+         (None , "stac-api" , 401 ), 
361+         ("" , "stac-api" , 401 ), 
362+         # Empty allowed audiences will regect tokens with an `aud` claim  
363+         ("any-api" , "" , 401 ), 
364+         ("any-api" , None , 401 ), 
365+         # Backward compatibility - no audience configured  
366+         (None , None , 200 ), 
367+         ("" , None , 200 ), 
368+     ], 
369+ ) 
370+ def  test_jwt_audience_validation (
371+     source_api_server ,
372+     token_builder ,
373+     token_audiences ,
374+     allowed_audiences ,
375+     expected_status ,
376+ ):
377+     """Test JWT audience validation with various configurations.""" 
378+     # Build app with audience configuration 
379+     app_factory  =  AppFactory (
380+         oidc_discovery_url = "https://example-stac-api.com/.well-known/openid-configuration" ,
381+         default_public = False ,
382+         allowed_jwt_audiences = allowed_audiences ,
383+     )
384+     test_app  =  app_factory (upstream_url = source_api_server )
385+ 
386+     # Build token with audience claim 
387+     token_payload  =  {}
388+     if  token_audiences  is  not None :
389+         token_payload ["aud" ] =  token_audiences 
390+ 
391+     valid_auth_token  =  token_builder (token_payload )
392+ 
393+     client  =  TestClient (test_app )
394+     response  =  client .get (
395+         "/collections" ,
396+         headers = {"Authorization" : f"Bearer { valid_auth_token }  },
397+     )
398+     assert  response .status_code  ==  expected_status 
399+ 
400+ 
401+ @pytest .mark .parametrize ( 
402+     "aud_value,scope,expected_status,description" , 
403+     [ 
404+         (["stac-api" ], "openid" , 401 , "Valid audience but missing scope" ), 
405+         (["stac-api" ], "collection:create" , 200 , "Valid audience and valid scope" ), 
406+         (["wrong-api" ], "collection:create" , 401 , "Invalid audience but valid scope" ), 
407+     ], 
408+ ) 
409+ def  test_audience_validation_with_scopes (
410+     source_api_server , token_builder , aud_value , scope , expected_status , description 
411+ ):
412+     """Test that audience validation works alongside scope validation.""" 
413+     app_factory  =  AppFactory (
414+         oidc_discovery_url = "https://example-stac-api.com/.well-known/openid-configuration" ,
415+         default_public = False ,
416+         allowed_jwt_audiences = "stac-api" ,
417+         private_endpoints = {r"^/collections$" : [("POST" , "collection:create" )]},
418+     )
419+     test_app  =  app_factory (upstream_url = source_api_server )
420+ 
421+     client  =  TestClient (test_app )
422+ 
423+     token  =  token_builder ({"aud" : aud_value , "scope" : scope })
424+     response  =  client .post (
425+         "/collections" ,
426+         headers = {"Authorization" : f"Bearer { token }  },
427+     )
428+     assert  response .status_code  ==  expected_status 
429+ 
430+ 
431+ @pytest .mark .parametrize ( 
432+     "allowed_audiences_config,test_audience,expected_status" , 
433+     [ 
434+         # Comma-separated string  
435+         ("stac-api,other-api" , "stac-api" , 200 ), 
436+         ("stac-api,other-api" , "other-api" , 200 ), 
437+         ("stac-api,other-api" , "unknown-api" , 401 ), 
438+         # Comma-separated with spaces  
439+         ("stac-api, other-api" , "stac-api" , 200 ), 
440+         ("stac-api, other-api" , "other-api" , 200 ), 
441+         ("stac-api, other-api" , "unknown-api" , 401 ), 
442+     ], 
443+ ) 
444+ def  test_allowed_audiences_configuration_formats (
445+     source_api_server ,
446+     token_builder ,
447+     allowed_audiences_config ,
448+     test_audience ,
449+     expected_status ,
450+ ):
451+     """Test different configuration formats for ALLOWED_JWT_AUDIENCES.""" 
452+     app_factory  =  AppFactory (
453+         oidc_discovery_url = "https://example-stac-api.com/.well-known/openid-configuration" ,
454+         default_public = False ,
455+         allowed_jwt_audiences = allowed_audiences_config ,
456+     )
457+     test_app  =  app_factory (upstream_url = source_api_server )
458+ 
459+     client  =  TestClient (test_app )
460+ 
461+     token  =  token_builder ({"aud" : [test_audience ]})
462+     response  =  client .get (
463+         "/collections" ,
464+         headers = {"Authorization" : f"Bearer { token }  },
465+     )
466+     assert  response .status_code  ==  expected_status 
0 commit comments