From 00895e7da73cb9ba5c152ac0d9ec8d43669f0f69 Mon Sep 17 00:00:00 2001 From: Paul Marillonnet Date: Wed, 18 Apr 2018 15:48:14 +0200 Subject: [PATCH 2/2] WIP federation file: lazy provider loading (#19396) --- mellon/utils.py | 13 ---------- mellon/views.py | 58 +++++++++++++++++++++++++++++++++++++++++---- tests/test_utils.py | 46 ++++++++++++++++++----------------- 3 files changed, 77 insertions(+), 40 deletions(-) diff --git a/mellon/utils.py b/mellon/utils.py index d505fb4..6487d23 100644 --- a/mellon/utils.py +++ b/mellon/utils.py @@ -76,19 +76,6 @@ def create_server(request): password = key[1] key = key[0] server.setEncryptionPrivateKeyWithPassword(key, password) - for idp in get_idps(): - try: - metadata = idp.get('METADATA') - if idp_metadata_is_file(metadata): - with open(metadata, 'r') as f: - metadata = f.read() - server.addProviderFromBuffer(lasso.PROVIDER_ROLE_IDP, metadata) - except lasso.Error, e: - logger.error(u'bad metadata in idp %r', idp) - logger.debug(u'lasso error: %s', e) - except IOError, e: - logger.warning('No such metadata file: %r', metadata) - continue return server diff --git a/mellon/views.py b/mellon/views.py index d6d58e9..62a5d75 100644 --- a/mellon/views.py +++ b/mellon/views.py @@ -17,7 +17,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME from django.db import transaction from django.utils.translation import ugettext as _ -from . import app_settings, utils +from . import app_settings, utils, federation_utils lasso.setFlag('thin-sessions') @@ -108,6 +108,7 @@ class LoginView(ProfileMixin, LogMixin, View): idp_message = None status_codes = [] # prevent null characters in SAMLResponse + import pdb; pdb.set_trace() try: login.processAuthnResponseMsg(request.POST['SAMLResponse']) login.acceptSso() @@ -120,6 +121,17 @@ class LoginView(ProfileMixin, LogMixin, View): lasso.ProfileStatusNotSuccessError, lasso.ProfileRequestDeniedError): self.show_message_status_is_not_success(login, 'SAML authentication failed') + except (lasso.ProfileUnknownProviderError, + lasso.ServerProviderNotFoundError): + try: + metadata = utils.get_idp(login.remoteProviderId) + if federation_utils.idp_metadata_is_file(metadata): + with open(metadata, 'r') as f: + metadata = f.read() + server = utils.create_server(request) + server.addProviderFromBuffer(lasso.PROVIDER_ROLE_IDP, metadata) + except lasso.Error as e: + return HttpResponseBadRequest('error processing the authentication response: %r' % e) except lasso.Error as e: return HttpResponseBadRequest('error processing the authentication response: %r' % e) else: @@ -240,15 +252,27 @@ class LoginView(ProfileMixin, LogMixin, View): self.profile = login = utils.create_login(request) if relay_state and utils.is_nonnull(relay_state): login.msgRelayState = relay_state + import pdb; pdb.set_trace() try: login.initRequest(message, method) except lasso.ProfileInvalidArtifactError: self.log.warning(u'artifact is malformed %r', artifact) return HttpResponseBadRequest(u'artifact is malformed %r' % artifact) - except lasso.ServerProviderNotFoundError: - self.log.warning('no entity id found for artifact %s', artifact) - return HttpResponseBadRequest( - 'no entity id found for this artifact %r' % artifact) + except (lasso.ProfileUnknownProviderError, + lasso.ServerProviderNotFoundError, + lasso.ProfileInvalidArtifactError): + try: + idp = utils.get_idp(login.remoteProviderId) + metadata = idp.get('METADATA') + if federation_utils.idp_metadata_is_file(metadata): + with open(metadata, 'r') as f: + metadata = f.read() + server = utils.create_server(request) + server.addProviderFromBuffer(lasso.PROVIDER_ROLE_IDP, metadata) + except lasso.Error as e: + self.log.warning('no entity id found for artifact %s', artifact) + return HttpResponseBadRequest( + 'no entity id found for this artifact %r' % artifact) idp = utils.get_idp(login.remoteProviderId) if not idp: self.log.warning('entity id %r is unknown', login.remoteProviderId) @@ -395,8 +419,20 @@ class LogoutView(ProfileMixin, LogMixin, View): def idp_logout(self, request): '''Handle logout request emitted by the IdP''' self.profile = logout = utils.create_logout(request) + import pdb; pdb.set_trace() try: logout.processRequestMsg(request.META['QUERY_STRING']) + except (lasso.ProfileUnknownProviderError, + lasso.ServerProviderNotFoundError): + try: + idp_metadata = utils.get_idp(logout.remoteProviderId) + if federation_utils.idp_metadata_is_file(idp_metadata): + with open(metadata, 'r') as f: + metadata = f.read() + server = utils.create_server(request) + server.addProviderFromBuffer(lasso.PROVIDER_ROLE_IDP, metadata) + except lasso.Error as e: + return HttpResponseBadRequest('error processing logout request: %r' % e) except lasso.Error as e: return HttpResponseBadRequest('error processing logout request: %r' % e) try: @@ -454,12 +490,24 @@ class LogoutView(ProfileMixin, LogMixin, View): # that a concurrent SSO happened in the meantime, so we do another # logout to make sure. auth.logout(request) + import pdb; pdb.set_trace() try: logout.processResponseMsg(request.META['QUERY_STRING']) except lasso.ProfileStatusNotSuccessError: self.show_message_status_is_not_success(logout, 'SAML logout failed') except lasso.LogoutPartialLogoutError: self.log.warning('partial logout') + except (lasso.ProfileUnknownProviderError, + lasso.ServerProviderNotFoundError): + try: + idp_metadata = utils.get_idp(logout.remoteProviderId) + if federation_utils.idp_metadata_is_file(idp_metadata): + with open(metadata, 'r') as f: + metadata = f.read() + server = utils.create_server(request) + server.addProviderFromBuffer(lasso.PROVIDER_ROLE_IDP, metadata) + except lasso.Error as e: + return HttpResponseBadRequest('error processing a logout request: %s' % e) except lasso.Error as e: self.log.warning('unable to process a logout response: %s', e) return HttpResponseRedirect(resolve_url(settings.LOGIN_REDIRECT_URL)) diff --git a/tests/test_utils.py b/tests/test_utils.py index e109b82..f6b570a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -17,7 +17,7 @@ from utils import error_500, metadata_response, sample_federation_response, \ html_response, dummy_md_response -def test_create_server_connection_error(mocker, rf, private_settings, caplog): +def test_create_server_connection_error_lazy(mocker, rf, private_settings, caplog): mocker.patch('requests.get', side_effect=requests.exceptions.ConnectionError('connection error')) private_settings.MELLON_IDENTITY_PROVIDERS = [ @@ -27,10 +27,10 @@ def test_create_server_connection_error(mocker, rf, private_settings, caplog): ] request = rf.get('/') create_server(request) - assert 'connection error' in caplog.text + assert 'connection error' not in caplog.text -def test_create_server_internal_server_error(mocker, rf, private_settings, caplog): +def test_create_server_internal_server_error_lazy(mocker, rf, private_settings, caplog): private_settings.MELLON_IDENTITY_PROVIDERS = [ { 'METADATA_URL': 'http://example.com/metadata', @@ -40,10 +40,10 @@ def test_create_server_internal_server_error(mocker, rf, private_settings, caplo assert not 'failed with error' in caplog.text with HTTMock(error_500): create_server(request) - assert 'failed with error' in caplog.text + assert 'failed with error' not in caplog.text -def test_load_federation_file(mocker, rf, private_settings, caplog, tmpdir): +def test_load_federation_file_lazy(mocker, rf, private_settings, caplog, tmpdir): private_settings.MELLON_FEDERATIONS = [ {'FEDERATION': 'tests/federation-sample.xml'}, ] @@ -51,10 +51,10 @@ def test_load_federation_file(mocker, rf, private_settings, caplog, tmpdir): assert 'failed with error' not in caplog.text with HTTMock(html_response): server = create_server(request) - assert len(server.providers) == 5 + assert len(server.providers) == 0 -def test_load_federation_url(mocker, rf, private_settings, caplog, tmpdir): +def test_load_federation_url_lazy(mocker, rf, private_settings, caplog, tmpdir): private_settings.MELLON_FEDERATIONS = [ {'FEDERATION': 'https://dummy.server/metadata.xml'}, ] @@ -62,10 +62,10 @@ def test_load_federation_url(mocker, rf, private_settings, caplog, tmpdir): assert 'failed with error' not in caplog.text with HTTMock(dummy_md_response): server = create_server(request) - assert len(server.providers) == 3 + assert len(server.providers) == 0 -def test_federation_parameters(mocker, rf, private_settings, caplog, tmpdir): +def test_federation_parameters_lazy(mocker, rf, private_settings, caplog, tmpdir): private_settings.MELLON_FEDERATIONS = [{ 'FEDERATION': 'tests/federation-sample.xml', 'VERIFY_SSL_CERTIFICATE': False, @@ -76,16 +76,18 @@ def test_federation_parameters(mocker, rf, private_settings, caplog, tmpdir): assert 'failed with error' not in caplog.text with HTTMock(html_response): server = create_server(request) - assert len(server.providers) == 5 + assert len(server.providers) == 0 + """ for entity_id in server.providers.keys(): idp = get_idp(entity_id) assert idp assert idp['VERIFY_SSL_CERTIFICATE'] is False assert idp['ERROR_REDIRECT_AFTER_TIMEOUT'] == 150 assert idp['PROVISION'] is True + """ -def test_create_server_invalid_metadata(mocker, rf, private_settings, caplog): +def test_create_server_invalid_metadata_lazy(mocker, rf, private_settings, caplog): caplog.set_level(logging.DEBUG) private_settings.MELLON_IDENTITY_PROVIDERS = [ { @@ -96,11 +98,11 @@ def test_create_server_invalid_metadata(mocker, rf, private_settings, caplog): assert not 'failed with error' in caplog.text with HTTMock(error_500): create_server(request) - assert len(caplog.records) == 4 - assert re.search('METADATA.*is invalid|bad metadata in idp', caplog.text) + assert len(caplog.records) == 0 + assert not re.search('METADATA.*is invalid|bad metadata in idp', caplog.text) -def test_create_server_invalid_metadata_file(mocker, rf, private_settings, caplog): +def test_create_server_invalid_metadata_file_lazy(mocker, rf, private_settings, caplog): private_settings.MELLON_IDENTITY_PROVIDERS = [ { 'METADATA': '/xxx', @@ -114,7 +116,7 @@ def test_create_server_invalid_metadata_file(mocker, rf, private_settings, caplo assert len(server.providers) == 0 -def test_create_server_good_metadata_file(mocker, rf, private_settings, caplog): +def test_create_server_good_metadata_file_lazy(mocker, rf, private_settings, caplog): private_settings.MELLON_IDENTITY_PROVIDERS = [ { 'METADATA': './tests/metadata.xml', @@ -124,10 +126,10 @@ def test_create_server_good_metadata_file(mocker, rf, private_settings, caplog): with HTTMock(html_response): server = create_server(request) assert 'ERROR' not in caplog.text - assert len(server.providers) == 1 + assert len(server.providers) == 0 -def test_create_server_good_metadata(mocker, rf, private_settings, caplog): +def test_create_server_good_metadata_lazy(mocker, rf, private_settings, caplog): private_settings.MELLON_IDENTITY_PROVIDERS = [ { 'METADATA': file('tests/metadata.xml').read(), @@ -137,10 +139,10 @@ def test_create_server_good_metadata(mocker, rf, private_settings, caplog): assert not 'failed with error' in caplog.text server = create_server(request) assert 'ERROR' not in caplog.text - assert len(server.providers) == 1 + assert len(server.providers) == 0 -def test_create_server_invalid_idp_dict(mocker, rf, private_settings, caplog): +def test_create_server_invalid_idp_dict_lazy(mocker, rf, private_settings, caplog): private_settings.MELLON_IDENTITY_PROVIDERS = [ { } @@ -148,10 +150,10 @@ def test_create_server_invalid_idp_dict(mocker, rf, private_settings, caplog): request = rf.get('/') assert not 'failed with error' in caplog.text create_server(request) - assert 'missing METADATA' in caplog.text + assert 'missing METADATA' not in caplog.text -def test_create_server_good_metadata_url(mocker, rf, private_settings, caplog): +def test_create_server_good_metadata_url_lazy(mocker, rf, private_settings, caplog): private_settings.MELLON_IDENTITY_PROVIDERS = [ { 'METADATA_URL': 'http://example.com/metadata', @@ -163,7 +165,7 @@ def test_create_server_good_metadata_url(mocker, rf, private_settings, caplog): with HTTMock(metadata_response): server = create_server(request) assert 'ERROR' not in caplog.text - assert len(server.providers) == 1 + assert len(server.providers) == 0 def test_create_metadata(rf, private_settings, caplog): -- 2.17.0