diff --git a/src/authentic2_auth_fedict/__init__.py b/src/authentic2_auth_fedict/__init__.py
index 5fc18e4..9186c8a 100644
--- a/src/authentic2_auth_fedict/__init__.py
+++ b/src/authentic2_auth_fedict/__init__.py
@@ -51,12 +51,6 @@ class Plugin:
def get_authentication_backends(self):
return ['authentic2_auth_fedict.backends.FedictBackend']
- def get_auth_frontends(self):
- return ['authentic2_auth_fedict.authenticators.FedictAuthenticator']
-
- def get_authenticators(self):
- return ['authentic2_auth_fedict.authenticators.FedictAuthenticator']
-
def redirect_logout_list(self, request, next_url=None):
from mellon.views import logout
diff --git a/src/authentic2_auth_fedict/app_settings.py b/src/authentic2_auth_fedict/app_settings.py
deleted file mode 100644
index fc62c4c..0000000
--- a/src/authentic2_auth_fedict/app_settings.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# authentic2_auth_fedict - Fedict authentication for Authentic
-# Copyright (C) 2016 Entr'ouvert
-#
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU Affero General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-
-class AppSettings:
- '''Thanks django-allauth'''
-
- __SENTINEL = object()
-
- def __init__(self, prefix):
- self.prefix = prefix
-
- def _setting(self, name, dflt=__SENTINEL):
- from django.conf import settings
- from django.core.exceptions import ImproperlyConfigured
-
- v = getattr(settings, self.prefix + name, dflt)
- if v is self.__SENTINEL:
- raise ImproperlyConfigured('Missing setting %r' % (self.prefix + name))
- return v
-
- @property
- def enable(self):
- return self._setting('ENABLE', False)
-
-
-import sys
-
-app_settings = AppSettings('A2_AUTH_FEDICT_')
-app_settings.__name__ = __name__
-sys.modules[__name__] = app_settings
diff --git a/src/authentic2_auth_fedict/forms.py b/src/authentic2_auth_fedict/forms.py
new file mode 100644
index 0000000..a514750
--- /dev/null
+++ b/src/authentic2_auth_fedict/forms.py
@@ -0,0 +1,26 @@
+# authentic2_auth_fedict - Fedict authentication for Authentic
+# Copyright (C) 2022 Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+from authentic2.apps.authenticators.forms import AuthenticatorFormMixin
+from django import forms
+
+from .models import FedictAuthenticator
+
+
+class FedictAuthenticatorForm(AuthenticatorFormMixin, forms.ModelForm):
+ class Meta:
+ model = FedictAuthenticator
+ exclude = ('name', 'slug', 'ou')
diff --git a/src/authentic2_auth_fedict/migrations/0001_initial.py b/src/authentic2_auth_fedict/migrations/0001_initial.py
new file mode 100644
index 0000000..8d5231e
--- /dev/null
+++ b/src/authentic2_auth_fedict/migrations/0001_initial.py
@@ -0,0 +1,36 @@
+# Generated by Django 2.2.26 on 2022-07-06 15:11
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('authenticators', '0003_auto_20220413_1504'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='FedictAuthenticator',
+ fields=[
+ (
+ 'baseauthenticator_ptr',
+ models.OneToOneField(
+ auto_created=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ parent_link=True,
+ primary_key=True,
+ serialize=False,
+ to='authenticators.BaseAuthenticator',
+ ),
+ ),
+ ],
+ options={
+ 'verbose_name': 'Belgian eID',
+ },
+ bases=('authenticators.baseauthenticator',),
+ ),
+ ]
diff --git a/src/authentic2_auth_fedict/migrations/0002_auto_20220706_1712.py b/src/authentic2_auth_fedict/migrations/0002_auto_20220706_1712.py
new file mode 100644
index 0000000..e168f39
--- /dev/null
+++ b/src/authentic2_auth_fedict/migrations/0002_auto_20220706_1712.py
@@ -0,0 +1,35 @@
+# Generated by Django 2.2.26 on 2022-07-06 15:12
+
+from django.conf import settings
+from django.db import migrations
+
+
+def create_fedict_authenticator(apps, schema_editor):
+ if not getattr(settings, 'A2_AUTH_FEDICT_ENABLE', False):
+ return
+
+ FedictAuthenticator = apps.get_model('authentic2_auth_fedict', 'FedictAuthenticator')
+
+ kwargs_settings = getattr(settings, 'AUTH_FRONTENDS_KWARGS', {})
+ authenticator_settings = kwargs_settings.get('fedict', {})
+
+ priority = authenticator_settings.get('priority')
+ priority = priority if priority is not None else -1
+
+ FedictAuthenticator.objects.create(
+ slug='fedict-authenticator',
+ order=priority,
+ show_condition=authenticator_settings.get('show_condition') or '',
+ enabled=True,
+ )
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('authentic2_auth_fedict', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RunPython(create_fedict_authenticator, reverse_code=migrations.RunPython.noop),
+ ]
diff --git a/src/authentic2_auth_fedict/migrations/__init__.py b/src/authentic2_auth_fedict/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/authentic2_auth_fedict/models.py b/src/authentic2_auth_fedict/models.py
index d21f3da..70b6066 100644
--- a/src/authentic2_auth_fedict/models.py
+++ b/src/authentic2_auth_fedict/models.py
@@ -14,7 +14,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from authentic2.authenticators import BaseAuthenticator
+from authentic2.apps.authenticators.models import BaseAuthenticator
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _
@@ -25,18 +25,19 @@ try:
except ImportError:
from authentic2.utils.misc import redirect_to_login
-from . import app_settings
-
class FedictAuthenticator(BaseAuthenticator):
- id = 'fedict'
- priority = -1
+ type = 'fedict'
+ unique = True
- def enabled(self):
- return app_settings.enable and list(get_idps())
+ class Meta:
+ verbose_name = _('Belgian eID')
- def name(self):
- return _('Belgian eID')
+ @property
+ def manager_form_class(self):
+ from .forms import FedictAuthenticatorForm
+
+ return FedictAuthenticatorForm
def login(self, request, *args, **kwargs):
context = kwargs.get('context', {}).copy()
diff --git a/src/authentic2_auth_fedict/signals.py b/src/authentic2_auth_fedict/signals.py
index bd51631..d4f8f49 100644
--- a/src/authentic2_auth_fedict/signals.py
+++ b/src/authentic2_auth_fedict/signals.py
@@ -16,12 +16,12 @@
from authentic2.models import Attribute, AttributeValue
-from . import app_settings
from .adapters import AuthenticAdapter
+from .models import FedictAuthenticator
def on_user_logged_in(sender, request, user, **kwargs):
- if not app_settings.enable:
+ if not FedictAuthenticator.objects.filter(enabled=True).exists():
return
if user.backend == 'authentic2_auth_fedict.backends.FedictBackend':
return
diff --git a/tests/settings.py b/tests/settings.py
index bf98fc7..ea5445e 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -18,7 +18,6 @@ if 'postgres' in DATABASES['default']['ENGINE']:
LANGUAGE_CODE = 'en'
A2_AUTH_SAML_ENABLE = False
-A2_AUTH_FEDICT_ENABLE = True
MELLON_ADAPTER = ["authentic2_auth_fedict.adapters.AuthenticAdapter"]
MELLON_LOGIN_URL = "fedict-login"
diff --git a/tests/test_all.py b/tests/test_all.py
index 64b2f45..4d5d723 100644
--- a/tests/test_all.py
+++ b/tests/test_all.py
@@ -8,12 +8,15 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
+from django.db import connection
+from django.db.migrations.executor import MigrationExecutor
from django.test.client import RequestFactory
from mellon.models import Issuer, UserSAMLIdentifier
from utils import login
from authentic2_auth_fedict.adapters import AuthenticAdapter
from authentic2_auth_fedict.backends import FedictBackend
+from authentic2_auth_fedict.models import FedictAuthenticator
User = get_user_model()
@@ -22,6 +25,11 @@ pytestmark = pytest.mark.django_db
factory = RequestFactory()
+@pytest.fixture
+def authenticator():
+ return FedictAuthenticator.objects.create(slug='fedict', enabled=True)
+
+
@pytest.fixture
def adapter():
return AuthenticAdapter()
@@ -81,7 +89,7 @@ def test_custom_lookup_user(adapter, idp_conf, issuer, fedict_attributes):
assert user.ou == get_default_ou()
-def test_login_fedict_authenticator_displayed(app, settings, issuer):
+def test_login_fedict_authenticator_displayed(app, settings, issuer, authenticator):
response = app.get('/login/')
assert 'Belgian eID' in response
assert 'csam-login' in response
@@ -175,7 +183,7 @@ def test_authenticate_eid_link_to_existing_user(app, settings, issuer, user):
assert backend_user == user
-def test_eid_unlink(app, settings, issuer, user):
+def test_eid_unlink(app, settings, issuer, user, authenticator):
assert len(UserSAMLIdentifier.objects.all()) == 0
# create link
@@ -282,3 +290,49 @@ def test_provision_old_account_deleted(app, settings, issuer, user):
# previous user as been deactivated
assert User.objects.count() == count - 1
assert not User.objects.filter(uuid=user_uuid)
+
+
+def test_fedict_authenticator_data_migration(settings):
+ app = 'authentic2_auth_fedict'
+ migrate_from = [(app, '0001_initial')]
+ migrate_to = [(app, '0002_auto_20220706_1712')]
+
+ executor = MigrationExecutor(connection)
+ old_apps = executor.loader.project_state(migrate_from).apps
+ executor.migrate(migrate_from)
+ FedictAuthenticator = old_apps.get_model(app, 'FedictAuthenticator')
+
+ settings.AUTH_FRONTENDS_KWARGS = {
+ "fedict": {"priority": 3, "show_condition": "'backoffice' not in login_hint"}
+ }
+ settings.A2_AUTH_FEDICT_ENABLE = True
+
+ executor = MigrationExecutor(connection)
+ executor.migrate(migrate_to)
+ executor.loader.build_graph()
+ new_apps = executor.loader.project_state(migrate_to).apps
+ FedictAuthenticator = new_apps.get_model(app, 'FedictAuthenticator')
+
+ authenticator = FedictAuthenticator.objects.get()
+ assert authenticator.slug == 'fedict-authenticator'
+ assert authenticator.order == 3
+ assert authenticator.show_condition == "'backoffice' not in login_hint"
+ assert authenticator.enabled is True
+
+
+def test_manager(app, admin):
+ resp = login(app, admin, path='/manage/authenticators/', index=0)
+
+ resp = resp.click('Add new authenticator')
+ resp.form['authenticator'] = 'fedict'
+ resp = resp.form.submit().follow()
+
+ authenticator = FedictAuthenticator.objects.get()
+ assert not authenticator.enabled
+
+ # on edit page, submit
+ resp = resp.form.submit().follow()
+ resp = resp.click('Enable')
+
+ authenticator.refresh_from_db()
+ assert authenticator.enabled
diff --git a/tests/utils.py b/tests/utils.py
index 42f9302..4fcff19 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -2,14 +2,14 @@ from authentic2.utils.misc import make_url
from django.urls import reverse
-def login(app, user, path=None, password=None, remember_me=None):
+def login(app, user, path=None, password=None, remember_me=None, index=1):
if path:
real_path = make_url(path)
login_page = app.get(real_path, status=302).maybe_follow()
else:
login_page = app.get(reverse('auth_login'))
assert login_page.request.path == reverse('auth_login')
- form = login_page.forms[1]
+ form = login_page.forms[index]
form.set('username', user.username if hasattr(user, 'username') else user)
# password is supposed to be the same as username
form.set('password', password or user.username)