From db0cd607d26885f59987c379e72d487d6667ab06 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Tue, 16 Aug 2022 16:57:02 +0200 Subject: [PATCH] authenticators: add advanced setup view (#67875) --- .../apps/authenticators/manager_urls.py | 5 ++++ .../authenticator_advanced_edit_form.html | 20 ++++++++++++++++ .../authenticator_edit_form.html | 13 ++++++++++ src/authentic2/apps/authenticators/views.py | 18 +++++++++++++- src/authentic2_auth_saml/forms.py | 24 ++++++++++++++++++- .../migrations/0001_initial.py | 4 ++-- src/authentic2_auth_saml/models.py | 11 ++++++--- tests/test_manager_authenticators.py | 18 +++++++++++--- 8 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 src/authentic2/apps/authenticators/templates/authentic2/authenticators/authenticator_advanced_edit_form.html diff --git a/src/authentic2/apps/authenticators/manager_urls.py b/src/authentic2/apps/authenticators/manager_urls.py index 664bf05c..7d85f326 100644 --- a/src/authentic2/apps/authenticators/manager_urls.py +++ b/src/authentic2/apps/authenticators/manager_urls.py @@ -61,6 +61,11 @@ urlpatterns = required( views.edit, name='a2-manager-authenticator-edit', ), + path( + 'authenticators//edit/advanced/', + views.edit_advanced, + name='a2-manager-authenticator-edit-advanced', + ), path( 'authenticators//delete/', views.delete, diff --git a/src/authentic2/apps/authenticators/templates/authentic2/authenticators/authenticator_advanced_edit_form.html b/src/authentic2/apps/authenticators/templates/authentic2/authenticators/authenticator_advanced_edit_form.html new file mode 100644 index 00000000..1c99fc76 --- /dev/null +++ b/src/authentic2/apps/authenticators/templates/authentic2/authenticators/authenticator_advanced_edit_form.html @@ -0,0 +1,20 @@ +{% extends "authentic2/authenticators/authenticator_common.html" %} +{% load i18n gadjo %} + +{% block breadcrumb %} + {{ block.super }} + {{ object }} + {% trans "Edit" %} + +{% endblock %} + +{% block content %} +
+ {% csrf_token %} + {{ form|with_template }} +
+ + {% trans 'Cancel' %} +
+
+{% endblock %} diff --git a/src/authentic2/apps/authenticators/templates/authentic2/authenticators/authenticator_edit_form.html b/src/authentic2/apps/authenticators/templates/authentic2/authenticators/authenticator_edit_form.html index b81d3dbc..b40448ab 100644 --- a/src/authentic2/apps/authenticators/templates/authentic2/authenticators/authenticator_edit_form.html +++ b/src/authentic2/apps/authenticators/templates/authentic2/authenticators/authenticator_edit_form.html @@ -7,6 +7,19 @@ {% endblock %} +{% block appbar %} + {{ block.super }} + + {% if object.manager_advanced_form_class %} + + + + + {% endif %} +{% endblock %} + {% block content %}
{% csrf_token %} diff --git a/src/authentic2/apps/authenticators/views.py b/src/authentic2/apps/authenticators/views.py index bb50fa56..6d9d95ea 100644 --- a/src/authentic2/apps/authenticators/views.py +++ b/src/authentic2/apps/authenticators/views.py @@ -16,7 +16,7 @@ from django.contrib import messages from django.core.exceptions import PermissionDenied -from django.http import HttpResponseRedirect +from django.http import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.urls import reverse, reverse_lazy from django.utils.functional import cached_property @@ -90,6 +90,22 @@ class AuthenticatorEditView(AuthenticatorsMixin, UpdateView): edit = AuthenticatorEditView.as_view() +class AuthenticatorAdvancedEditView(AuthenticatorEditView): + template_name = 'authentic2/authenticators/authenticator_advanced_edit_form.html' + title = _('Edit authenticator (advanced)') + + def dispatch(self, *args, **kwargs): + if not hasattr(self.get_object(), 'manager_advanced_form_class'): + raise Http404() + return super().dispatch(*args, **kwargs) + + def get_form_class(self): + return self.object.manager_advanced_form_class + + +edit_advanced = AuthenticatorAdvancedEditView.as_view() + + class AuthenticatorDeleteView(AuthenticatorsMixin, DeleteView): template_name = 'authentic2/authenticators/authenticator_delete_form.html' title = _('Delete authenticator') diff --git a/src/authentic2_auth_saml/forms.py b/src/authentic2_auth_saml/forms.py index 0840189f..4e39b843 100644 --- a/src/authentic2_auth_saml/forms.py +++ b/src/authentic2_auth_saml/forms.py @@ -25,7 +25,29 @@ from .models import SAMLAuthenticator class SAMLAuthenticatorForm(forms.ModelForm): class Meta: model = SAMLAuthenticator - exclude = ('ou',) + exclude = ( + 'ou', + 'verify_ssl_certificate', + 'transient_federation_attribute', + 'realm', + 'username_template', + 'name_id_policy_format', + 'name_id_policy_allow_create', + 'force_authn', + 'add_authnrequest_next_url_extension', + 'group_attribute', + 'create_group', + 'error_url', + 'error_redirect_after_timeout', + 'authn_classref', + 'attribute_mapping', + ) + + +class SAMLAuthenticatorAdvancedForm(forms.ModelForm): + class Meta: + model = SAMLAuthenticator + fields = SAMLAuthenticatorForm.Meta.exclude class RoleChoiceField(forms.ModelChoiceField): diff --git a/src/authentic2_auth_saml/migrations/0001_initial.py b/src/authentic2_auth_saml/migrations/0001_initial.py index 0e84d451..3025abd2 100644 --- a/src/authentic2_auth_saml/migrations/0001_initial.py +++ b/src/authentic2_auth_saml/migrations/0001_initial.py @@ -81,9 +81,9 @@ class Migration(migrations.Migration): 'realm', models.CharField( default='saml', - help_text='The default realm to associate to user, used when setting username.', + help_text='The default realm to associate to user, can be used in username template.', max_length=32, - verbose_name='Realm', + verbose_name='Realm (realm)', ), ), ( diff --git a/src/authentic2_auth_saml/models.py b/src/authentic2_auth_saml/models.py index 94ab25fd..ed7ed51f 100644 --- a/src/authentic2_auth_saml/models.py +++ b/src/authentic2_auth_saml/models.py @@ -57,9 +57,9 @@ class SAMLAuthenticator(BaseAuthenticator): blank=True, ) realm = models.CharField( - _('Realm'), + _('Realm (realm)'), max_length=32, - help_text=_('The default realm to associate to user, used when setting username.'), + help_text=_('The default realm to associate to user, can be used in username template.'), default='saml', ) username_template = models.CharField( @@ -153,7 +153,6 @@ class SAMLAuthenticator(BaseAuthenticator): 'metadata_path', 'metadata', 'provision', - 'username_template', ] class Meta: @@ -181,6 +180,12 @@ class SAMLAuthenticator(BaseAuthenticator): return SAMLAuthenticatorForm + @property + def manager_advanced_form_class(self): + from .forms import SAMLAuthenticatorAdvancedForm + + return SAMLAuthenticatorAdvancedForm + def clean(self): if not (self.metadata or self.metadata_path or self.metadata_url): raise ValidationError(_('One of the metadata fields must be filled.')) diff --git a/tests/test_manager_authenticators.py b/tests/test_manager_authenticators.py index 0fdb6a67..d065ca3e 100644 --- a/tests/test_manager_authenticators.py +++ b/tests/test_manager_authenticators.py @@ -99,6 +99,11 @@ def test_authenticators_password(app, superuser): resp = app.get('/manage/authenticators/add/') assert 'Password' not in resp.text + # password authenticator has no advanced setup form + app.get('/manage/authenticators/%s/edit/advanced/' % authenticator.pk, status=404) + app.get('/manage/authenticators/%s/edit/' % authenticator.pk) + assert 'Advanced setup' not in resp.text + @pytest.mark.freeze_time('2022-04-19 14:00') def test_authenticators_oidc(app, superuser, ou1, ou2): @@ -254,7 +259,6 @@ def test_authenticators_saml(app, superuser, ou1, ou2): authenticator = SAMLAuthenticator.objects.filter(slug='test').get() 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 @@ -262,19 +266,27 @@ def test_authenticators_saml(app, superuser, ou1, ou2): assert 'configuration is not complete' in resp.text resp = resp.click('Edit') + assert 'attribute_mapping' not in resp.form.fields + assert 'realm' 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['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 + resp = resp.click('Edit') + resp = resp.click('Advanced setup') + resp.form['attribute_mapping'] = '[{"attribute": "email", "saml_attribute": "mail", "mandatory": false}]' + resp.form['realm'] = 'abc' + resp = resp.form.submit().follow() + authenticator.refresh_from_db() assert authenticator.attribute_mapping == [ {"attribute": "email", "saml_attribute": "mail", "mandatory": False} ] + assert authenticator.realm == 'abc' resp = resp.click('Enable').follow() assert 'Authenticator has been enabled.' in resp.text -- 2.30.2