@@ -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