@@ -405,46 +405,98 @@ def test_load_idp_dynamic_entity_id(self, idp_conf):
405
405
assert idp .config .entityid == "{}/{}" .format (idp_conf ["entityid" ], self .TARGET_ENTITY_ID )
406
406
407
407
408
- class TestSAMLVirtualCoFrontend :
408
+ class TestSAMLVirtualCoFrontend ( TestSAMLFrontend ) :
409
409
BACKEND = "test_backend"
410
410
CO = "MESS"
411
+ CO_O = "organization"
412
+ CO_C = "countryname"
413
+ CO_CO = "friendlycountryname"
414
+ CO_NOREDUORGACRONYM = "noreduorgacronym"
415
+ CO_STATIC_SAML_ATTRIBUTES = {
416
+ CO_O : ["Medium Energy Synchrotron Source" ],
417
+ CO_C : ["US" ],
418
+ CO_CO : ["United States" ],
419
+ CO_NOREDUORGACRONYM : ["MESS" ]
420
+ }
411
421
KEY_SSO = "single_sign_on_service"
412
422
413
- @pytest .fixture (autouse = True )
414
- def create_frontend (self , idp_conf ):
415
- collab_orgs = [{"encodeable_name" : self .CO }]
423
+ @pytest .fixture
424
+ def frontend (self , idp_conf , sp_conf ):
425
+ """
426
+ This fixture is an instance of the SAMLVirtualCoFrontend with an IdP
427
+ configuration that includes SAML metadata for the test SP configured
428
+ by the sp_conf fixture so that we can test a SAML Response sent
429
+ from the IdP.
430
+ """
431
+ # Use a utility function to serialize the sp_conf fixture as
432
+ # a string and then dynamically add it as the metadata available
433
+ # as part of the idp_conf fixture.
434
+ sp_metadata_str = create_metadata_from_config_dict (sp_conf )
435
+ idp_conf ["metadata" ]["inline" ] = [sp_metadata_str ]
436
+
437
+ # Dynamically add configuration details for the CO including static
438
+ # SAML attributes so their presence in a SAML Response can be tested.
439
+ collab_org = {
440
+ "encodeable_name" : self .CO ,
441
+ "co_static_saml_attributes" : self .CO_STATIC_SAML_ATTRIBUTES
442
+ }
443
+
444
+ # Use the dynamically updated idp_conf fixture, the configured
445
+ # endpoints, and the collaborative organization configuration to
446
+ # create the configuration for the frontend.
416
447
conf = {
417
448
"idp_config" : idp_conf ,
418
449
"endpoints" : ENDPOINTS ,
419
- "collaborative_organizations" : collab_orgs
450
+ "collaborative_organizations" : [ collab_org ]
420
451
}
421
- self .frontend = SAMLVirtualCoFrontend (lambda ctx , req : None ,
422
- INTERNAL_ATTRIBUTES ,
423
- conf ,
424
- BASE_URL ,
425
- "saml_virtual_co_frontend" )
426
- self .frontend .register_endpoints ([self .BACKEND ])
427
452
428
- @pytest .fixture (autouse = True )
429
- def create_context (self , context ):
453
+ # Use a richer set of internal attributes than what is provided
454
+ # for the parent class so that we can test for the static SAML
455
+ # attributes about the CO being asserted.
456
+ internal_attributes = INTERNAL_ATTRIBUTES
457
+ internal_attributes ["attributes" ][self .CO_O ] = {"saml" : ["o" ]}
458
+ internal_attributes ["attributes" ][self .CO_C ] = {"saml" : ["c" ]}
459
+ internal_attributes ["attributes" ][self .CO_CO ] = {"saml" : ["co" ]}
460
+ internal_attributes ["attributes" ][self .CO_NOREDUORGACRONYM ] = (
461
+ {"saml" : ["norEduOrgAcronym" ]})
462
+
463
+ # Create, register the endpoints, and then return the frontend
464
+ # instance.
465
+ frontend = SAMLVirtualCoFrontend (lambda ctx , req : None ,
466
+ internal_attributes ,
467
+ conf ,
468
+ BASE_URL ,
469
+ "saml_virtual_co_frontend" )
470
+ frontend .register_endpoints ([self .BACKEND ])
471
+
472
+ return frontend
473
+
474
+ @pytest .fixture
475
+ def context (self , context ):
476
+ """
477
+ This fixture is an instance of the context that mocks up the context
478
+ that would be available during a SAML flow and that would include
479
+ a path and target_backend that indicates the CO.
480
+ """
430
481
context .path = "{}/{}/sso/redirect" .format (self .BACKEND , self .CO )
431
482
context .target_backend = self .BACKEND
432
- self .context = context
433
483
434
- def test_create_state_data (self ):
435
- self .context .decorate (self .frontend .KEY_CO_NAME , self .CO )
436
- state = self .frontend ._create_state_data (self .context , {}, "" )
437
- assert state [self .frontend .KEY_CO_NAME ] == self .CO
484
+ return context
485
+
486
+ def test_create_state_data (self , frontend , context ):
487
+ context .decorate (frontend .KEY_CO_NAME , self .CO )
488
+ state = frontend ._create_state_data (context , {}, "" )
489
+ assert state [frontend .KEY_CO_NAME ] == self .CO
438
490
439
- def test_get_co_name (self ):
440
- co_name = self . frontend ._get_co_name (self . context )
491
+ def test_get_co_name (self , frontend , context ):
492
+ co_name = frontend ._get_co_name (context )
441
493
assert co_name == self .CO
442
494
443
- self . frontend ._create_state_data (self . context , {}, "" )
444
- co_name = self . frontend ._get_co_name (self . context )
495
+ frontend ._create_state_data (context , {}, "" )
496
+ co_name = frontend ._get_co_name (context )
445
497
assert co_name == self .CO
446
498
447
- def test_create_co_virtual_idp (self , idp_conf ):
499
+ def test_create_co_virtual_idp (self , frontend , context , idp_conf ):
448
500
expected_entityid = "{}/{}" .format (idp_conf ['entityid' ], self .CO )
449
501
450
502
endpoint_base_url = "{}/{}/{}" .format (BASE_URL , self .BACKEND , self .CO )
@@ -453,15 +505,15 @@ def test_create_co_virtual_idp(self, idp_conf):
453
505
endp = "{}/{}" .format (endpoint_base_url , endpoint )
454
506
expected_endpoints .append ((endp , binding ))
455
507
456
- idp_server = self . frontend ._create_co_virtual_idp (self . context )
508
+ idp_server = frontend ._create_co_virtual_idp (context )
457
509
sso_endpoints = idp_server .config ._idp_endpoints [self .KEY_SSO ]
458
510
459
511
assert idp_server .config .entityid == expected_entityid
460
512
assert all (sso in sso_endpoints for sso in expected_endpoints )
461
513
462
- def test_register_endpoints (self ):
463
- idp_server = self . frontend ._create_co_virtual_idp (self . context )
464
- url_map = self . frontend .register_endpoints ([self .BACKEND ])
514
+ def test_register_endpoints (self , frontend , context ):
515
+ idp_server = frontend ._create_co_virtual_idp (context )
516
+ url_map = frontend .register_endpoints ([self .BACKEND ])
465
517
all_idp_endpoints = [urlparse (endpoint [0 ]).path [1 :] for
466
518
endpoint in
467
519
idp_server .config ._idp_endpoints [self .KEY_SSO ]]
@@ -470,6 +522,65 @@ def test_register_endpoints(self):
470
522
for endpoint in all_idp_endpoints :
471
523
assert any (pat .match (endpoint ) for pat in compiled_regex )
472
524
525
+ def test_co_static_attributes (self , frontend , context , internal_response ,
526
+ idp_conf , sp_conf ):
527
+ # Use the frontend and context fixtures to dynamically create the
528
+ # proxy IdP server that would be created during a flow.
529
+ idp_server = frontend ._create_co_virtual_idp (context )
530
+
531
+ # Use the context fixture to find the CO name and the backend name
532
+ # and then use those to dynamically update the ipd_conf fixture.
533
+ co_name = frontend ._get_co_name (context )
534
+ backend_name = context .target_backend
535
+ idp_conf = frontend ._add_endpoints_to_config (idp_conf , co_name ,
536
+ backend_name )
537
+ idp_conf = frontend ._add_entity_id (idp_conf , co_name )
538
+
539
+ # Use a utility function to serialize the idp_conf IdP configuration
540
+ # fixture to a string and then dynamically update the sp_conf
541
+ # SP configuration fixture with the metadata.
542
+ idp_metadata_str = create_metadata_from_config_dict (idp_conf )
543
+ sp_conf ["metadata" ]["inline" ].append (idp_metadata_str )
544
+ sp_config = SPConfig ().load (sp_conf , metadata_construction = False )
545
+
546
+ # Use the updated sp_config fixture to generate a fake SP and then
547
+ # use the fake SP to generate an authentication request aimed at the
548
+ # proxy CO virtual IdP.
549
+ fakesp = FakeSP (sp_config )
550
+ destination , auth_req = fakesp .make_auth_req (
551
+ idp_server .config .entityid ,
552
+ nameid_format = None ,
553
+ relay_state = "relay_state" ,
554
+ subject = None ,
555
+ )
556
+
557
+ # Update the context with the authentication request.
558
+ context .request = auth_req
559
+
560
+ # Create the response arguments necessary for the IdP to respond to
561
+ # the authentication request, update the request state and with it
562
+ # the context, and then use the frontend fixture and the
563
+ # internal_response fixture to handle the authentication response
564
+ # and generate a response from the proxy IdP to the SP.
565
+ resp_args = {
566
+ "name_id_policy" : NameIDPolicy (format = NAMEID_FORMAT_TRANSIENT ),
567
+ "in_response_to" : None ,
568
+ "destination" : sp_config .endpoint (
569
+ "assertion_consumer_service" ,
570
+ binding = BINDING_HTTP_REDIRECT
571
+ )[0 ],
572
+ "sp_entity_id" : sp_conf ["entityid" ],
573
+ "binding" : BINDING_HTTP_REDIRECT
574
+ }
575
+ request_state = frontend ._create_state_data (context , resp_args , "" )
576
+ context .state [frontend .name ] = request_state
577
+ frontend .handle_authn_response (context , internal_response )
578
+
579
+ # Verify that the frontend added the CO static SAML attributes to the
580
+ # internal response.
581
+ for attr , value in self .CO_STATIC_SAML_ATTRIBUTES .items ():
582
+ assert internal_response .attributes [attr ] == value
583
+
473
584
474
585
class TestSubjectTypeToSamlNameIdFormat :
475
586
def test_should_default_to_persistent (self ):
0 commit comments