From fbc3588f1b90a056841babef4a3e1164f653023a Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 5 Aug 2021 13:34:59 +0200 Subject: [PATCH] add MELLON_ASSERTION_CONSUMER_BINDINGS (#52063) The default value is ['post', 'artifact']. --- README | 7 +++++ mellon/app_settings.py | 1 + mellon/templates/mellon/metadata.xml | 6 ++-- mellon/utils.py | 1 + mellon/views.py | 8 +++++- tests/test_utils.py | 42 ++++++++++++++++++++++++++++ 6 files changed, 61 insertions(+), 4 deletions(-) diff --git a/README b/README index 16180a8..a1206c3 100644 --- a/README +++ b/README @@ -311,6 +311,13 @@ MELLON_METADATA_HTTP_TIMEOUT Timeout in seconds for HTTP call made to retrieve metadata files. Default is 10 seconds. +MELLON_ASSERTION_CONSUMER_BINDINGS +---------------------------------- + +The list of supported assertion consumer bindings. Default is:: + + ['post', 'artifact'] + Tests ===== diff --git a/mellon/app_settings.py b/mellon/app_settings.py index a177abf..103efea 100644 --- a/mellon/app_settings.py +++ b/mellon/app_settings.py @@ -26,6 +26,7 @@ class AppSettings: 'CREATE_GROUP': True, 'ERROR_URL': None, 'ERROR_REDIRECT_AFTER_TIMEOUT': 120, + 'ASSERTION_CONSUMER_BINDINGS': ['post', 'artifact'], 'DEFAULT_ASSERTION_CONSUMER_BINDING': 'post', # or artifact 'VERIFY_SSL_CERTIFICATE': True, 'OPENED_SESSION_COOKIE_NAME': None, diff --git a/mellon/templates/mellon/metadata.xml b/mellon/templates/mellon/metadata.xml index 333f98e..ceaaaa1 100644 --- a/mellon/templates/mellon/metadata.xml +++ b/mellon/templates/mellon/metadata.xml @@ -32,21 +32,21 @@ {% for name_id_format in name_id_formats %} {{ name_id_format }} {% endfor %} - - - +{% endif %} {% if organization and organization.NAMES and organization.DISPLAY_NAMES and organization.URLS %} {% for name in organization.NAMES %} diff --git a/mellon/utils.py b/mellon/utils.py index a05fcae..ef9d8f7 100644 --- a/mellon/utils.py +++ b/mellon/utils.py @@ -54,6 +54,7 @@ def create_metadata(request): 'logout_url': request.build_absolute_uri(logout_url), 'public_keys': public_keys, 'name_id_formats': name_id_formats, + 'assertion_consumer_bindings': app_settings.ASSERTION_CONSUMER_BINDINGS, 'default_assertion_consumer_binding': app_settings.DEFAULT_ASSERTION_CONSUMER_BINDING, 'organization': app_settings.ORGANIZATION, 'contact_persons': app_settings.CONTACT_PERSONS, diff --git a/mellon/views.py b/mellon/views.py index aac7a75..660624c 100644 --- a/mellon/views.py +++ b/mellon/views.py @@ -29,7 +29,7 @@ from django.conf import settings from django.contrib import auth from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model from django.db import transaction -from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect +from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseRedirect from django.shortcuts import render, resolve_url from django.urls import reverse from django.utils import six @@ -163,8 +163,12 @@ class LoginView(ProfileMixin, LogMixin, View): def post(self, request, *args, **kwargs): '''Assertion consumer''' if 'SAMLart' in request.POST: + if 'artifact' not in app_settings.ASSERTION_CONSUMER_BINDINGS: + raise Http404('artifact binding is not supported') return self.continue_sso_artifact(request, lasso.HTTP_METHOD_ARTIFACT_POST) if 'SAMLResponse' not in request.POST: + if 'post' not in app_settings.ASSERTION_CONSUMER_BINDINGS: + raise Http404('post binding is not supported') return self.get(request, *args, **kwargs) if not utils.is_nonnull(request.POST['SAMLResponse']): return HttpResponseBadRequest('SAMLResponse contains a null character') @@ -462,6 +466,8 @@ class LoginView(ProfileMixin, LogMixin, View): def get(self, request, *args, **kwargs): '''Initialize login request''' if 'SAMLart' in request.GET: + if 'artifact' not in app_settings.ASSERTION_CONSUMER_BINDINGS: + raise Http404('artifact binding is not supported') return self.continue_sso_artifact(request, lasso.HTTP_METHOD_ARTIFACT_GET) # redirect to discovery service if needed diff --git a/tests/test_utils.py b/tests/test_utils.py index d4f38cb..718779a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -112,6 +112,48 @@ def test_create_metadata(rf, private_settings, caplog): namespaces=ns, ) + private_settings.MELLON_ASSERTION_CONSUMER_BINDINGS = ['post'] + with mock.patch('mellon.utils.open', mock.mock_open(read_data='BEGIN\nyyy\nEND'), create=True): + metadata = create_metadata(request) + assert 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + assert_xml_constraints( + metadata.encode('utf-8'), + ( + '/sm:EntityDescriptor/sm:SPSSODescriptor', + 1, + ( + '/sm:AssertionConsumerService[@Binding=\'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\']', + 0, + ), + ( + '/sm:AssertionConsumerService[@Binding=\'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\']', + 1, + ), + ), + namespaces=ns, + ) + + private_settings.MELLON_ASSERTION_CONSUMER_BINDINGS = ['artifact'] + with mock.patch('mellon.utils.open', mock.mock_open(read_data='BEGIN\nyyy\nEND'), create=True): + metadata = create_metadata(request) + assert 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + assert_xml_constraints( + metadata.encode('utf-8'), + ( + '/sm:EntityDescriptor/sm:SPSSODescriptor', + 1, + ( + '/sm:AssertionConsumerService[@Binding=\'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\']', + 1, + ), + ( + '/sm:AssertionConsumerService[@Binding=\'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\']', + 0, + ), + ), + namespaces=ns, + ) + def test_iso8601_to_datetime(private_settings): import django.utils.timezone