@@ -980,6 +980,44 @@ def testIsInValidAudience(self):
980980 self .assertFalse (response_2 .is_valid (request_data ))
981981 self .assertIn ('is not a valid audience for this Response' , response_2 .get_error ())
982982
983+ def testIsInValidAuthenticationContext (self ):
984+ """
985+ Tests that requestedAuthnContext, when set, is compared against the
986+ response AuthnContext, which is what you use for two-factor
987+ authentication. Without this check you can get back a valid response
988+ that didn't complete the two-factor step.
989+ """
990+ request_data = self .get_request_data ()
991+ message = self .file_contents (join (self .data_path , 'responses' , 'valid_response.xml.base64' ))
992+ two_factor_context = 'urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken'
993+ password_context = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password'
994+ settings_dict = self .loadSettingsJSON ()
995+ settings_dict ['security' ]['requestedAuthnContext' ] = [two_factor_context ]
996+ settings_dict ['security' ]['failOnAuthnContextMismatch' ] = True
997+ settings_dict ['strict' ] = True
998+ settings = OneLogin_Saml2_Settings (settings_dict )
999+
1000+ # check that we catch when the contexts don't match
1001+ response = OneLogin_Saml2_Response (settings , message )
1002+ self .assertFalse (response .is_valid (request_data ))
1003+ self .assertIn ('The AuthnContext "%s" didn\' t include requested context "%s"' % (password_context , two_factor_context ), response .get_error ())
1004+
1005+ # now drop in the expected AuthnContextClassRef and see that it passes
1006+ original_message = b64decode (message )
1007+ two_factor_message = original_message .replace (password_context , two_factor_context )
1008+ two_factor_message = b64encode (two_factor_message )
1009+ response = OneLogin_Saml2_Response (settings , two_factor_message )
1010+ response .is_valid (request_data )
1011+ # check that we got as far as destination validation, which comes later
1012+ self .assertIn ('The response was received at' , response .get_error ())
1013+
1014+ # with the default setting, check that we succeed with our original context
1015+ settings_dict ['security' ]['requestedAuthnContext' ] = True
1016+ settings = OneLogin_Saml2_Settings (settings_dict )
1017+ response = OneLogin_Saml2_Response (settings , message )
1018+ response .is_valid (request_data )
1019+ self .assertIn ('The response was received at' , response .get_error ())
1020+
9831021 def testIsInValidIssuer (self ):
9841022 """
9851023 Tests the is_valid method of the OneLogin_Saml2_Response class
0 commit comments