From f29205fdce4908c8be42bd722f33a3cf86de4ecf Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Wed, 17 Aug 2022 18:16:08 +0200 Subject: [PATCH] auth_saml: prefer metadata file upload over metadata path (#67451) --- src/authentic2/apps/authenticators/models.py | 16 +++++++++++----- src/authentic2_auth_saml/forms.py | 5 +++++ .../0008_samlauthenticator_metadata_file.py | 18 ++++++++++++++++++ src/authentic2_auth_saml/models.py | 7 ++++++- tests/test_manager_authenticators.py | 16 +++++++++++++--- 5 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 src/authentic2_auth_saml/migrations/0008_samlauthenticator_metadata_file.py diff --git a/src/authentic2/apps/authenticators/models.py b/src/authentic2/apps/authenticators/models.py index 4c23c97d..2c1edb6a 100644 --- a/src/authentic2/apps/authenticators/models.py +++ b/src/authentic2/apps/authenticators/models.py @@ -20,8 +20,11 @@ import uuid from django.core.exceptions import ValidationError from django.db import models +from django.db.models.fields.files import FieldFile from django.shortcuts import render, reverse from django.utils.formats import date_format +from django.utils.html import format_html +from django.utils.safestring import mark_safe from django.utils.text import capfirst from django.utils.translation import pgettext_lazy from django.utils.translation import ugettext_lazy as _ @@ -111,11 +114,14 @@ class BaseAuthenticator(models.Model): if isinstance(value, datetime.datetime): value = date_format(value, 'DATETIME_FORMAT') - - yield _('%(field)s: %(value)s') % { - 'field': capfirst(self._meta.get_field(field).verbose_name), - 'value': value, - } + elif isinstance(value, FieldFile): + value = mark_safe(f'{value.name}') + + yield format_html( + _('{field}: {value}'), + field=capfirst(self._meta.get_field(field).verbose_name), + value=value, + ) def shown(self, ctx=()): if not self.show_condition: diff --git a/src/authentic2_auth_saml/forms.py b/src/authentic2_auth_saml/forms.py index 0840189f..80974a42 100644 --- a/src/authentic2_auth_saml/forms.py +++ b/src/authentic2_auth_saml/forms.py @@ -27,6 +27,11 @@ class SAMLAuthenticatorForm(forms.ModelForm): model = SAMLAuthenticator exclude = ('ou',) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if not self.initial.get('metadata_path'): + del self.fields['metadata_path'] + class RoleChoiceField(forms.ModelChoiceField): def __init__(self, *args, **kwargs): diff --git a/src/authentic2_auth_saml/migrations/0008_samlauthenticator_metadata_file.py b/src/authentic2_auth_saml/migrations/0008_samlauthenticator_metadata_file.py new file mode 100644 index 00000000..b6918324 --- /dev/null +++ b/src/authentic2_auth_saml/migrations/0008_samlauthenticator_metadata_file.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.26 on 2022-08-24 13:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentic2_auth_saml', '0007_remove_jsonfields'), + ] + + operations = [ + migrations.AddField( + model_name='samlauthenticator', + name='metadata_file', + field=models.FileField(blank=True, null=True, upload_to=''), + ), + ] diff --git a/src/authentic2_auth_saml/models.py b/src/authentic2_auth_saml/models.py index ba6bccda..20280ad1 100644 --- a/src/authentic2_auth_saml/models.py +++ b/src/authentic2_auth_saml/models.py @@ -30,6 +30,7 @@ class SAMLAuthenticator(BaseAuthenticator): metadata_cache_time = models.PositiveSmallIntegerField(_('Metadata cache time'), default=3600) metadata_http_timeout = models.PositiveSmallIntegerField(_('Metadata HTTP timeout'), default=10) + metadata_file = models.FileField(null=True, blank=True) metadata_path = models.CharField( _('Metadata file path'), max_length=300, @@ -147,6 +148,7 @@ class SAMLAuthenticator(BaseAuthenticator): description_fields = [ 'show_condition', 'metadata_url', + 'metadata_file', 'metadata_path', 'metadata', 'provision', @@ -166,6 +168,9 @@ class SAMLAuthenticator(BaseAuthenticator): if not settings[setting]: del settings[setting] + if self.metadata_file: + settings['METADATA_PATH'] = self.metadata_file.path + settings['LOOKUP_BY_ATTRIBUTES'] = [lookup.as_dict() for lookup in self.attribute_lookups.all()] settings['authenticator'] = self @@ -178,7 +183,7 @@ class SAMLAuthenticator(BaseAuthenticator): return SAMLAuthenticatorForm def clean(self): - if not (self.metadata or self.metadata_path or self.metadata_url): + if not (self.metadata or self.metadata_path or self.metadata_url or self.metadata_file): raise ValidationError(_('One of the metadata fields must be filled.')) def autorun(self, request, block_id): diff --git a/tests/test_manager_authenticators.py b/tests/test_manager_authenticators.py index 0fdb6a67..dc380e36 100644 --- a/tests/test_manager_authenticators.py +++ b/tests/test_manager_authenticators.py @@ -17,6 +17,7 @@ import pytest from django import VERSION as DJ_VERSION from django.utils.html import escape +from webtest import Upload from authentic2.a2_rbac.utils import get_default_ou from authentic2.apps.authenticators.models import BaseAuthenticator, LoginPasswordAuthenticator @@ -256,20 +257,22 @@ def test_authenticators_saml(app, superuser, ou1, ou2): resp = app.get(authenticator.get_absolute_url()) assert 'Username template: {attributes[name_id_content]}@{realm}' in resp.text assert 'Provision: True' in resp.text - assert 'Metadata file path' not in resp.text + assert 'Metadata file' not in resp.text assert 'Enable' not in resp.text assert 'configuration is not complete' in resp.text resp = resp.click('Edit') + assert 'metadata_path' not in resp.form.fields + resp = resp.form.submit() assert 'One of the metadata fields must be filled.' in resp.text - resp.form['metadata_path'] = '/var/lib/authentic2/metadata.xml' + resp.form['metadata_file'] = Upload('metadata.xml', b'some-xml', 'text/xml') resp.form['attribute_mapping'] = '[{"attribute": "email", "saml_attribute": "mail", "mandatory": false}]' resp = resp.form.submit().follow() - assert 'Metadata file path: /var/lib/authentic2/metadata.xml' in resp.text + assert 'Metadata file: metadata.xml' in resp.text authenticator.refresh_from_db() assert authenticator.attribute_mapping == [ @@ -279,6 +282,13 @@ def test_authenticators_saml(app, superuser, ou1, ou2): resp = resp.click('Enable').follow() assert 'Authenticator has been enabled.' in resp.text + authenticator.metadata_file = None + authenticator.metadata_path = '/var/lib/authentic2/metadata.xml' + authenticator.save() + + resp = resp.click('Edit') + assert 'metadata_path' in resp.form.fields + def test_authenticators_saml_attribute_lookup(app, superuser): authenticator = SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1') -- 2.30.2