add MELLON_ASSERTION_CONSUMER_BINDINGS (#52063)

The default value is ['post', 'artifact'].
This commit is contained in:
Benjamin Dauvergne 2021-08-05 13:34:59 +02:00
parent 734a7bb51b
commit fbc3588f1b
6 changed files with 61 additions and 4 deletions

7
README
View File

@ -311,6 +311,13 @@ MELLON_METADATA_HTTP_TIMEOUT
Timeout in seconds for HTTP call made to retrieve metadata files. Default is 10 Timeout in seconds for HTTP call made to retrieve metadata files. Default is 10
seconds. seconds.
MELLON_ASSERTION_CONSUMER_BINDINGS
----------------------------------
The list of supported assertion consumer bindings. Default is::
['post', 'artifact']
Tests Tests
===== =====

View File

@ -26,6 +26,7 @@ class AppSettings:
'CREATE_GROUP': True, 'CREATE_GROUP': True,
'ERROR_URL': None, 'ERROR_URL': None,
'ERROR_REDIRECT_AFTER_TIMEOUT': 120, 'ERROR_REDIRECT_AFTER_TIMEOUT': 120,
'ASSERTION_CONSUMER_BINDINGS': ['post', 'artifact'],
'DEFAULT_ASSERTION_CONSUMER_BINDING': 'post', # or artifact 'DEFAULT_ASSERTION_CONSUMER_BINDING': 'post', # or artifact
'VERIFY_SSL_CERTIFICATE': True, 'VERIFY_SSL_CERTIFICATE': True,
'OPENED_SESSION_COOKIE_NAME': None, 'OPENED_SESSION_COOKIE_NAME': None,

View File

@ -32,21 +32,21 @@
{% for name_id_format in name_id_formats %} {% for name_id_format in name_id_formats %}
<NameIDFormat>{{ name_id_format }}</NameIDFormat> <NameIDFormat>{{ name_id_format }}</NameIDFormat>
{% endfor %} {% endfor %}
<AssertionConsumerService {% if 'artifact' in assertion_consumer_bindings %} <AssertionConsumerService
index="0" index="0"
{% if default_assertion_consumer_binding == "artifact" %} {% if default_assertion_consumer_binding == "artifact" %}
isDefault="true" isDefault="true"
{% endif %} {% endif %}
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
Location="{{ login_url }}" /> Location="{{ login_url }}" />
<AssertionConsumerService {% endif %}{% if 'post' in assertion_consumer_bindings %} <AssertionConsumerService
index="1" index="1"
{% if default_assertion_consumer_binding == "post" %} {% if default_assertion_consumer_binding == "post" %}
isDefault="true" isDefault="true"
{% endif %} {% endif %}
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="{{ login_url }}" /> Location="{{ login_url }}" />
</SPSSODescriptor> {% endif %} </SPSSODescriptor>
{% if organization and organization.NAMES and organization.DISPLAY_NAMES and organization.URLS %} {% if organization and organization.NAMES and organization.DISPLAY_NAMES and organization.URLS %}
<Organization> <Organization>
{% for name in organization.NAMES %} {% for name in organization.NAMES %}

View File

@ -54,6 +54,7 @@ def create_metadata(request):
'logout_url': request.build_absolute_uri(logout_url), 'logout_url': request.build_absolute_uri(logout_url),
'public_keys': public_keys, 'public_keys': public_keys,
'name_id_formats': name_id_formats, 'name_id_formats': name_id_formats,
'assertion_consumer_bindings': app_settings.ASSERTION_CONSUMER_BINDINGS,
'default_assertion_consumer_binding': app_settings.DEFAULT_ASSERTION_CONSUMER_BINDING, 'default_assertion_consumer_binding': app_settings.DEFAULT_ASSERTION_CONSUMER_BINDING,
'organization': app_settings.ORGANIZATION, 'organization': app_settings.ORGANIZATION,
'contact_persons': app_settings.CONTACT_PERSONS, 'contact_persons': app_settings.CONTACT_PERSONS,

View File

@ -29,7 +29,7 @@ from django.conf import settings
from django.contrib import auth from django.contrib import auth
from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model
from django.db import transaction 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.shortcuts import render, resolve_url
from django.urls import reverse from django.urls import reverse
from django.utils import six from django.utils import six
@ -163,8 +163,12 @@ class LoginView(ProfileMixin, LogMixin, View):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
'''Assertion consumer''' '''Assertion consumer'''
if 'SAMLart' in request.POST: 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) return self.continue_sso_artifact(request, lasso.HTTP_METHOD_ARTIFACT_POST)
if 'SAMLResponse' not in request.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) return self.get(request, *args, **kwargs)
if not utils.is_nonnull(request.POST['SAMLResponse']): if not utils.is_nonnull(request.POST['SAMLResponse']):
return HttpResponseBadRequest('SAMLResponse contains a null character') return HttpResponseBadRequest('SAMLResponse contains a null character')
@ -462,6 +466,8 @@ class LoginView(ProfileMixin, LogMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
'''Initialize login request''' '''Initialize login request'''
if 'SAMLart' in request.GET: 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) return self.continue_sso_artifact(request, lasso.HTTP_METHOD_ARTIFACT_GET)
# redirect to discovery service if needed # redirect to discovery service if needed

View File

@ -112,6 +112,48 @@ def test_create_metadata(rf, private_settings, caplog):
namespaces=ns, 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): def test_iso8601_to_datetime(private_settings):
import django.utils.timezone import django.utils.timezone