diff --git a/common/djangoapps/third_party_auth/saml.py b/common/djangoapps/third_party_auth/saml.py index 38fa157bb4..f31d48f004 100644 --- a/common/djangoapps/third_party_auth/saml.py +++ b/common/djangoapps/third_party_auth/saml.py @@ -90,15 +90,15 @@ class SAMLAuthBackend(SAMLAuth): # pylint: disable=abstract-method """ try: return super().get_user_id(details, response) - except KeyError as ex: + except (KeyError, IndexError) as ex: log.warning( '[THIRD_PARTY_AUTH] Error in SAML authentication flow. ' 'Provider: {idp_name}, Message: {message}'.format( - message=ex.message, # lint-amnesty, pylint: disable=no-member + message=str(ex), idp_name=response.get('idp_name') ) ) - raise IncorrectConfigurationException(self) # lint-amnesty, pylint: disable=raise-missing-from + raise IncorrectConfigurationException(self) from ex def generate_metadata_xml(self, idp_name=None): # pylint: disable=arguments-differ """ diff --git a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py index 1c552d7fb0..76305871ed 100644 --- a/common/djangoapps/third_party_auth/tests/specs/test_testshib.py +++ b/common/djangoapps/third_party_auth/tests/specs/test_testshib.py @@ -22,6 +22,7 @@ from social_django.models import UserSocialAuth from testfixtures import LogCapture from common.djangoapps.third_party_auth import pipeline +from common.djangoapps.third_party_auth.exceptions import IncorrectConfigurationException from common.djangoapps.third_party_auth.saml import SapSuccessFactorsIdentityProvider from common.djangoapps.third_party_auth.saml import log as saml_log from common.djangoapps.third_party_auth.tasks import fetch_saml_metadata @@ -141,6 +142,82 @@ class SamlIntegrationTestUtilities: ) +@ddt.ddt +@utils.skip_unless_thirdpartyauth() +class TestIndexExceptionTest(SamlIntegrationTestUtilities, IntegrationTestMixin, testutil.SAMLTestCase): + """ + To test SAML error handling when presented with an empty-list attribute value + """ + + TOKEN_RESPONSE_DATA = { + 'access_token': 'access_token_value', + 'expires_in': 'expires_in_value', + } + USER_RESPONSE_DATA = { + 'lastName': 'lastName_value', + 'id': 'id_value', + 'firstName': 'firstName_value', + 'idp_name': 'testshib', + 'attributes': {'urn:oid:0.9.2342.19200300.100.1.1': [], 'name_id': '1'}, + 'session_index': '1', + } + + def test_index_error_from_empty_list_saml_attribute(self): + """ + The `urn:oid:0.9.2342.19200300.100.1.1` attribute is an empty list, + should throw a specific exception NOT an IndexException + """ + self.provider = self._configure_testshib_provider() + request, strategy = self.get_request_and_strategy( + auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete') + with self.assertRaises(IncorrectConfigurationException): + request.backend.auth_complete = MagicMock(return_value=self.fake_auth_complete(strategy)) + + def get_response_data(self): + """Gets dict (string -> object) of merged data about the user.""" + response_data = dict(self.TOKEN_RESPONSE_DATA) + response_data.update(self.USER_RESPONSE_DATA) + return response_data + + +@ddt.ddt +@utils.skip_unless_thirdpartyauth() +class TestKeyExceptionTest(SamlIntegrationTestUtilities, IntegrationTestMixin, testutil.SAMLTestCase): + """ + To test SAML error handling when presented with missing attributes + """ + + TOKEN_RESPONSE_DATA = { + 'access_token': 'access_token_value', + 'expires_in': 'expires_in_value', + } + USER_RESPONSE_DATA = { + 'lastName': 'lastName_value', + 'id': 'id_value', + 'firstName': 'firstName_value', + 'idp_name': 'testshib', + 'attributes': {'name_id': '1'}, + 'session_index': '1', + } + + def test_key_error_from_missing_saml_attributes(self): + """ + The `urn:oid:0.9.2342.19200300.100.1.1` attribute is missing, + should throw a specific exception NOT a Key Error + """ + self.provider = self._configure_testshib_provider() + request, strategy = self.get_request_and_strategy( + auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete') + with self.assertRaises(IncorrectConfigurationException): + request.backend.auth_complete = MagicMock(return_value=self.fake_auth_complete(strategy)) + + def get_response_data(self): + """Gets dict (string -> object) of merged data about the user.""" + response_data = dict(self.TOKEN_RESPONSE_DATA) + response_data.update(self.USER_RESPONSE_DATA) + return response_data + + @ddt.ddt @utils.skip_unless_thirdpartyauth() class TestShibIntegrationTest(SamlIntegrationTestUtilities, IntegrationTestMixin, testutil.SAMLTestCase):