From 9cd165ea9ab92ae88b9028c17bfae7610bedbde1 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Thu, 20 Oct 2022 16:56:49 +0200 Subject: [PATCH 1/2] auth_saml: validate xml metadata (#70492) --- .../migrations/0001_initial.py | 11 ++++++++- src/authentic2_auth_saml/models.py | 19 ++++++++++++++- tests/test_manager_authenticators.py | 24 +++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/authentic2_auth_saml/migrations/0001_initial.py b/src/authentic2_auth_saml/migrations/0001_initial.py index 5186c1209..cabaf4ddd 100644 --- a/src/authentic2_auth_saml/migrations/0001_initial.py +++ b/src/authentic2_auth_saml/migrations/0001_initial.py @@ -4,6 +4,8 @@ import django.contrib.postgres.fields.jsonb import django.db.models.deletion from django.db import migrations, models +import authentic2_auth_saml.models + class Migration(migrations.Migration): @@ -46,7 +48,14 @@ class Migration(migrations.Migration): verbose_name='Metadata file path', ), ), - ('metadata', models.TextField(blank=True, verbose_name='Metadata (XML)')), + ( + 'metadata', + models.TextField( + blank=True, + validators=[authentic2_auth_saml.models.validate_metadata], + verbose_name='Metadata (XML)', + ), + ), ( 'provision', models.BooleanField( diff --git a/src/authentic2_auth_saml/models.py b/src/authentic2_auth_saml/models.py index a5d22fb64..8730998fd 100644 --- a/src/authentic2_auth_saml/models.py +++ b/src/authentic2_auth_saml/models.py @@ -14,6 +14,9 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import xml.etree.ElementTree as ET + +import lasso from django.conf import settings from django.contrib.postgres.fields import JSONField from django.core.exceptions import ValidationError @@ -28,6 +31,20 @@ from authentic2.apps.authenticators.models import ( from authentic2.utils.misc import redirect_to_login +def validate_metadata(metadata): + try: + doc = ET.fromstring(metadata) + except (TypeError, ET.ParseError) as e: + raise ValidationError(_('Cannot parse metadata, %s') % e) + + tag_name = '{%s}EntityDescriptor' % lasso.SAML2_METADATA_HREF + if doc.tag != tag_name: + raise ValidationError(_('Invalid metadata, missing tag %s') % tag_name) + + if 'entityID' not in doc.attrib: + raise ValidationError(_('Invalid metadata, missing entityID')) + + class SAMLAuthenticator(BaseAuthenticator): metadata_url = models.URLField(_('Metadata URL'), max_length=300, blank=True) metadata_cache_time = models.PositiveSmallIntegerField(_('Metadata cache time'), default=3600) @@ -39,7 +56,7 @@ class SAMLAuthenticator(BaseAuthenticator): help_text=_('Absolute path to the IdP metadata file.'), blank=True, ) - metadata = models.TextField(_('Metadata (XML)'), blank=True) + metadata = models.TextField(_('Metadata (XML)'), blank=True, validators=[validate_metadata]) provision = models.BooleanField(_('Create user if their username does not already exists'), default=True) verify_ssl_certificate = models.BooleanField( diff --git a/tests/test_manager_authenticators.py b/tests/test_manager_authenticators.py index 0c59e8da7..53bb78b25 100644 --- a/tests/test_manager_authenticators.py +++ b/tests/test_manager_authenticators.py @@ -481,6 +481,30 @@ def test_authenticators_saml_hide_metadata_url_advanced_fields(app, superuser, o assert 'Metadata HTTP timeout' in resp.text +def test_authenticators_saml_validate_metadata(app, superuser): + authenticator = SAMLAuthenticator.objects.create(slug='idp1') + + resp = login(app, superuser) + resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk) + resp.form['metadata'] = 'invalid' + + resp.form['metadata'] = '' + resp = resp.form.submit() + assert 'Invalid metadata, missing tag {urn:oasis:names:tc:SAML:2.0:metadata}EntityDescriptor' in resp.text + + resp.form[ + 'metadata' + ] = '' + resp = resp.form.submit() + assert 'Invalid metadata, missing entityID' in resp.text + + resp.form['metadata'] = ( + '' + ) + resp.form.submit(status=302) + + def test_authenticators_saml_missing_signing_key(app, superuser, settings): authenticator = SAMLAuthenticator.objects.create(slug='idp1') -- 2.35.1