From 33d305f7a7edfc2f3c3c362f8971950ccd8de944 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Fri, 26 Feb 2016 13:15:42 +0100 Subject: [PATCH] add support for Organization and ContactPerson elements in metadata (fixes #6656) --- mellon/app_settings.py | 2 + mellon/templates/mellon/metadata.xml | 46 ++++++++++++ mellon/utils.py | 2 + tests/conftest.py | 13 ++++ tests/test_views.py | 106 +++++++++++++++++++++++++++ 5 files changed, 169 insertions(+) diff --git a/mellon/app_settings.py b/mellon/app_settings.py index cb3de72..ecece59 100644 --- a/mellon/app_settings.py +++ b/mellon/app_settings.py @@ -30,6 +30,8 @@ class AppSettings(object): 'VERIFY_SSL_CERTIFICATE': True, 'OPENED_SESSION_COOKIE_NAME': None, 'OPENED_SESSION_COOKIE_DOMAIN': None, + 'ORGANIZATION': None, + 'CONTACT_PERSONS': [], } @property diff --git a/mellon/templates/mellon/metadata.xml b/mellon/templates/mellon/metadata.xml index b5051e6..633907f 100644 --- a/mellon/templates/mellon/metadata.xml +++ b/mellon/templates/mellon/metadata.xml @@ -36,5 +36,51 @@ Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="{{ login_url }}" /> + {% if organization and organization.NAMES and organization.DISPLAY_NAMES and organization.URLS %} + + {% for name in organization.NAMES %} + {% if name.LABEL %} + {{ name.LABEL }} + {% else %} + {{ name }} + {% endif %} + {% endfor %} + {% for display_name in organization.DISPLAY_NAMES %} + {% if display_name.LABEL %} + {{ display_name.LABEL }} + {% else %} + {{ display_name }} + {% endif %} + {% endfor %} + {% for url in organization.URLS %} + {% if url.URL %} + {{ url.URL }} + {% else %} + {{ url }} + {% endif %} + {% endfor %} + + {% endif %} + {% for contact_person in contact_persons %} + {% if contact_person.CONTACT_TYPE %} + + {% if contact_person.COMPANY %} + {{ contact_person.COMPANY }} + {% endif %} + {% if contact_person.GIVEN_NAME %} + {{ contact_person.GIVEN_NAME }} + {% endif %} + {% if contact_person.SURNAME %} + {{ contact_person.SURNAME }} + {% endif %} + {% for email_address in contact_person.EMAIL_ADDRESSES %} + {{ email_address }} + {% endfor %} + {% for telephone_number in contact_person.TELEPHONE_NUMBERS %} + {{ telephone_number }} + {% endfor %} + + {% endif %} + {% endfor %} diff --git a/mellon/utils.py b/mellon/utils.py index b5f9956..d18a1d2 100644 --- a/mellon/utils.py +++ b/mellon/utils.py @@ -36,6 +36,8 @@ def create_metadata(request): 'public_keys': public_keys, 'name_id_formats': name_id_formats, 'default_assertion_consumer_binding': app_settings.DEFAULT_ASSERTION_CONSUMER_BINDING, + 'organization': app_settings.ORGANIZATION, + 'contact_persons': app_settings.CONTACT_PERSONS, }) settings._MELLON_METADATA_CACHE = cache return settings._MELLON_METADATA_CACHE[entity_id] diff --git a/tests/conftest.py b/tests/conftest.py index 9c3c8a2..9bdb738 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,3 +19,16 @@ def concurrency(settings): return 20 else: return 100 + + +@pytest.fixture +def private_settings(request): + import django.conf + from django.conf import UserSettingsHolder + old = django.conf.settings._wrapped + django.conf.settings._wrapped = UserSettingsHolder(old) + + def finalizer(): + django.conf.settings._wrapped = old + request.addfinalizer(finalizer) + return django.conf.settings diff --git a/tests/test_views.py b/tests/test_views.py index 7d8deef..deb9edd 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,4 +1,110 @@ +import mock +import lasso + from django.core.urlresolvers import reverse +from xml_utils import assert_xml_constraints + + def test_null_character_on_samlresponse_post(app): app.post(reverse('mellon_login'), {'SAMLResponse': '\x00'}, status=400) + + +def test_metadata(private_settings, client): + ns = { + 'sm': 'urn:oasis:names:tc:SAML:2.0:metadata', + 'ds': 'http://www.w3.org/2000/09/xmldsig#', + } + private_settings.MELLON_PUBLIC_KEYS = ['xxx', '/yyy'] + private_settings.MELLON_NAME_ID_FORMATS = [lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED] + private_settings.MELLON_DEFAULT_ASSERTION_CONSUMER_BINDING = 'artifact' + private_settings.MELLON_ORGANIZATION = { + 'NAMES': [ + 'Foobar', + { + 'LABEL': 'FoobarEnglish', + 'LANG': 'en', + } + ], + 'DISPLAY_NAMES': [ + 'Foobar', + { + 'LABEL': 'FoobarEnglish', + 'LANG': 'en', + } + ], + 'URLS': [ + 'http://foobar.com/', + { + 'URL': 'http://foobar.com/en/', + 'LANG': 'en', + } + ], + } + private_settings.MELLON_CONTACT_PERSONS = [ + { + 'CONTACT_TYPE': 'administrative', + 'COMPANY': 'FooBar', + 'GIVENNAME': 'John', + 'SURNAME': 'Doe', + 'EMAIL_ADDRESSES': [ + 'john.doe@foobar.com', + 'john.doe@personal-email.com', + ], + 'TELEPHONE_NUMBERS': [ + '+abcd', + '+1234', + ], + }, + { + 'CONTACT_TYPE': 'technical', + 'COMPANY': 'FooBar2', + 'GIVENNAME': 'John', + 'SURNAME': 'Doe', + 'EMAIL_ADDRESSES': [ + 'john.doe@foobar.com', + 'john.doe@personal-email.com', + ], + 'TELEPHONE_NUMBERS': [ + '+abcd', + '+1234', + ], + }, + ] + + with mock.patch('mellon.utils.file', mock.mock_open(read_data='BEGIN\nyyy\nEND'), create=True): + response = client.get('/metadata/') + assert_xml_constraints( + response.content, + ('/sm:EntityDescriptor[@entityID="http://testserver/metadata/"]', 1, + ('/*', 4), + ('/sm:SPSSODescriptor', 1, + ('/*', 6), + ('/sm:NameIDFormat', 1), + ('/sm:SingleLogoutService', 1), + ('/sm:AssertionConsumerService', None, + ('[@isDefault="true"]', None, + ('[@Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"]', 1), + ('[@Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"]', 0)), + ('[@Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"]', 1)), + ('/sm:KeyDescriptor/ds:KeyInfo/ds:X509Data', 2, + ('/ds:X509Certificate', 2), + ('/ds:X509Certificate[text()="xxx"]', 1), + ('/ds:X509Certificate[text()="yyy"]', 1))), + ('/sm:Organization', 1, + ('/sm:OrganizationName', 2), + ('/sm:OrganizationName[text()="Foobar"]', 1), + ('/sm:OrganizationName[text()="FoobarEnglish"]', 1, + ('[@xml:lang="en"]', 1)), + ('/sm:OrganizationDisplayName', 2), + ('/sm:OrganizationDisplayName[text()="Foobar"]', 1), + ('/sm:OrganizationDisplayName[text()="FoobarEnglish"]', 1, + ('[@xml:lang="en"]', 1)), + ('/sm:OrganizationURL', 2), + ('/sm:OrganizationURL[text()="http://foobar.com/"]', 1), + ('/sm:OrganizationURL[text()="http://foobar.com/en/"]', 1, + ('[@xml:lang="en"]', 1))), + ('/sm:ContactPerson', 2, + ('[@contactType="technical"]', 1), + ('[@contactType="administrative"]', 1))), + namespaces=ns)