0002-migrate-authenticator-to-database-66876.patch
src/authentic2_auth_fedict/__init__.py | ||
---|---|---|
51 | 51 |
def get_authentication_backends(self): |
52 | 52 |
return ['authentic2_auth_fedict.backends.FedictBackend'] |
53 | 53 | |
54 |
def get_auth_frontends(self): |
|
55 |
return ['authentic2_auth_fedict.authenticators.FedictAuthenticator'] |
|
56 | ||
57 |
def get_authenticators(self): |
|
58 |
return ['authentic2_auth_fedict.authenticators.FedictAuthenticator'] |
|
59 | ||
60 | 54 |
def redirect_logout_list(self, request, next_url=None): |
61 | 55 |
from mellon.views import logout |
62 | 56 |
src/authentic2_auth_fedict/app_settings.py | ||
---|---|---|
1 |
# authentic2_auth_fedict - Fedict authentication for Authentic |
|
2 |
# Copyright (C) 2016 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 | ||
18 |
class AppSettings: |
|
19 |
'''Thanks django-allauth''' |
|
20 | ||
21 |
__SENTINEL = object() |
|
22 | ||
23 |
def __init__(self, prefix): |
|
24 |
self.prefix = prefix |
|
25 | ||
26 |
def _setting(self, name, dflt=__SENTINEL): |
|
27 |
from django.conf import settings |
|
28 |
from django.core.exceptions import ImproperlyConfigured |
|
29 | ||
30 |
v = getattr(settings, self.prefix + name, dflt) |
|
31 |
if v is self.__SENTINEL: |
|
32 |
raise ImproperlyConfigured('Missing setting %r' % (self.prefix + name)) |
|
33 |
return v |
|
34 | ||
35 |
@property |
|
36 |
def enable(self): |
|
37 |
return self._setting('ENABLE', False) |
|
38 | ||
39 | ||
40 |
import sys |
|
41 | ||
42 |
app_settings = AppSettings('A2_AUTH_FEDICT_') |
|
43 |
app_settings.__name__ = __name__ |
|
44 |
sys.modules[__name__] = app_settings |
src/authentic2_auth_fedict/forms.py | ||
---|---|---|
1 |
# authentic2_auth_fedict - Fedict authentication for Authentic |
|
2 |
# Copyright (C) 2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from authentic2.apps.authenticators.forms import AuthenticatorFormMixin |
|
18 |
from django import forms |
|
19 | ||
20 |
from .models import FedictAuthenticator |
|
21 | ||
22 | ||
23 |
class FedictAuthenticatorForm(AuthenticatorFormMixin, forms.ModelForm): |
|
24 |
class Meta: |
|
25 |
model = FedictAuthenticator |
|
26 |
exclude = ('name', 'slug', 'ou') |
src/authentic2_auth_fedict/migrations/0001_initial.py | ||
---|---|---|
1 |
# Generated by Django 2.2.26 on 2022-07-06 15:11 |
|
2 | ||
3 |
import django.db.models.deletion |
|
4 |
from django.db import migrations, models |
|
5 | ||
6 | ||
7 |
class Migration(migrations.Migration): |
|
8 | ||
9 |
initial = True |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('authenticators', '0003_auto_20220413_1504'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.CreateModel( |
|
17 |
name='FedictAuthenticator', |
|
18 |
fields=[ |
|
19 |
( |
|
20 |
'baseauthenticator_ptr', |
|
21 |
models.OneToOneField( |
|
22 |
auto_created=True, |
|
23 |
on_delete=django.db.models.deletion.CASCADE, |
|
24 |
parent_link=True, |
|
25 |
primary_key=True, |
|
26 |
serialize=False, |
|
27 |
to='authenticators.BaseAuthenticator', |
|
28 |
), |
|
29 |
), |
|
30 |
], |
|
31 |
options={ |
|
32 |
'verbose_name': 'Belgian eID', |
|
33 |
}, |
|
34 |
bases=('authenticators.baseauthenticator',), |
|
35 |
), |
|
36 |
] |
src/authentic2_auth_fedict/migrations/0002_auto_20220706_1712.py | ||
---|---|---|
1 |
# Generated by Django 2.2.26 on 2022-07-06 15:12 |
|
2 | ||
3 |
from django.conf import settings |
|
4 |
from django.db import migrations |
|
5 | ||
6 | ||
7 |
def create_fedict_authenticator(apps, schema_editor): |
|
8 |
if not getattr(settings, 'A2_AUTH_FEDICT_ENABLE', False): |
|
9 |
return |
|
10 | ||
11 |
FedictAuthenticator = apps.get_model('authentic2_auth_fedict', 'FedictAuthenticator') |
|
12 | ||
13 |
kwargs_settings = getattr(settings, 'AUTH_FRONTENDS_KWARGS', {}) |
|
14 |
authenticator_settings = kwargs_settings.get('fedict', {}) |
|
15 | ||
16 |
priority = authenticator_settings.get('priority') |
|
17 |
priority = priority if priority is not None else -1 |
|
18 | ||
19 |
FedictAuthenticator.objects.create( |
|
20 |
slug='fedict-authenticator', |
|
21 |
order=priority, |
|
22 |
show_condition=authenticator_settings.get('show_condition') or '', |
|
23 |
enabled=True, |
|
24 |
) |
|
25 | ||
26 | ||
27 |
class Migration(migrations.Migration): |
|
28 | ||
29 |
dependencies = [ |
|
30 |
('authentic2_auth_fedict', '0001_initial'), |
|
31 |
] |
|
32 | ||
33 |
operations = [ |
|
34 |
migrations.RunPython(create_fedict_authenticator, reverse_code=migrations.RunPython.noop), |
|
35 |
] |
src/authentic2_auth_fedict/models.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
from authentic2.authenticators import BaseAuthenticator
|
|
17 |
from authentic2.apps.authenticators.models import BaseAuthenticator
|
|
18 | 18 |
from django.shortcuts import render |
19 | 19 |
from django.template.loader import render_to_string |
20 | 20 |
from django.utils.translation import ugettext_lazy as _ |
... | ... | |
25 | 25 |
except ImportError: |
26 | 26 |
from authentic2.utils.misc import redirect_to_login |
27 | 27 | |
28 |
from . import app_settings |
|
29 | ||
30 | 28 | |
31 | 29 |
class FedictAuthenticator(BaseAuthenticator): |
32 |
id = 'fedict' |
|
33 |
priority = -1 |
|
30 |
type = 'fedict' |
|
31 |
unique = True |
|
32 | ||
33 |
class Meta: |
|
34 |
verbose_name = _('Belgian eID') |
|
34 | 35 | |
35 |
def enabled(self): |
|
36 |
return app_settings.enable and list(get_idps()) |
|
36 |
@property |
|
37 |
def manager_form_class(self): |
|
38 |
from .forms import FedictAuthenticatorForm |
|
37 | 39 | |
38 |
def name(self): |
|
39 |
return _('Belgian eID') |
|
40 |
return FedictAuthenticatorForm |
|
40 | 41 | |
41 | 42 |
def login(self, request, *args, **kwargs): |
42 | 43 |
context = kwargs.get('context', {}).copy() |
src/authentic2_auth_fedict/signals.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
from authentic2.models import Attribute, AttributeValue |
18 | 18 | |
19 |
from . import app_settings |
|
20 | 19 |
from .adapters import AuthenticAdapter |
20 |
from .models import FedictAuthenticator |
|
21 | 21 | |
22 | 22 | |
23 | 23 |
def on_user_logged_in(sender, request, user, **kwargs): |
24 |
if not app_settings.enable:
|
|
24 |
if not FedictAuthenticator.objects.filter(enabled=True).exists():
|
|
25 | 25 |
return |
26 | 26 |
if user.backend == 'authentic2_auth_fedict.backends.FedictBackend': |
27 | 27 |
return |
tests/settings.py | ||
---|---|---|
18 | 18 | |
19 | 19 |
LANGUAGE_CODE = 'en' |
20 | 20 |
A2_AUTH_SAML_ENABLE = False |
21 |
A2_AUTH_FEDICT_ENABLE = True |
|
22 | 21 | |
23 | 22 |
MELLON_ADAPTER = ["authentic2_auth_fedict.adapters.AuthenticAdapter"] |
24 | 23 |
MELLON_LOGIN_URL = "fedict-login" |
tests/test_all.py | ||
---|---|---|
8 | 8 |
from django.contrib.auth.models import AnonymousUser |
9 | 9 |
from django.contrib.messages.middleware import MessageMiddleware |
10 | 10 |
from django.contrib.sessions.middleware import SessionMiddleware |
11 |
from django.db import connection |
|
12 |
from django.db.migrations.executor import MigrationExecutor |
|
11 | 13 |
from django.test.client import RequestFactory |
12 | 14 |
from mellon.models import Issuer, UserSAMLIdentifier |
13 | 15 |
from utils import login |
14 | 16 | |
15 | 17 |
from authentic2_auth_fedict.adapters import AuthenticAdapter |
16 | 18 |
from authentic2_auth_fedict.backends import FedictBackend |
19 |
from authentic2_auth_fedict.models import FedictAuthenticator |
|
17 | 20 | |
18 | 21 |
User = get_user_model() |
19 | 22 | |
... | ... | |
22 | 25 |
factory = RequestFactory() |
23 | 26 | |
24 | 27 | |
28 |
@pytest.fixture |
|
29 |
def authenticator(): |
|
30 |
return FedictAuthenticator.objects.create(slug='fedict', enabled=True) |
|
31 | ||
32 | ||
25 | 33 |
@pytest.fixture |
26 | 34 |
def adapter(): |
27 | 35 |
return AuthenticAdapter() |
... | ... | |
81 | 89 |
assert user.ou == get_default_ou() |
82 | 90 | |
83 | 91 | |
84 |
def test_login_fedict_authenticator_displayed(app, settings, issuer): |
|
92 |
def test_login_fedict_authenticator_displayed(app, settings, issuer, authenticator):
|
|
85 | 93 |
response = app.get('/login/') |
86 | 94 |
assert 'Belgian eID' in response |
87 | 95 |
assert 'csam-login' in response |
... | ... | |
175 | 183 |
assert backend_user == user |
176 | 184 | |
177 | 185 | |
178 |
def test_eid_unlink(app, settings, issuer, user): |
|
186 |
def test_eid_unlink(app, settings, issuer, user, authenticator):
|
|
179 | 187 |
assert len(UserSAMLIdentifier.objects.all()) == 0 |
180 | 188 | |
181 | 189 |
# create link |
... | ... | |
282 | 290 |
# previous user as been deactivated |
283 | 291 |
assert User.objects.count() == count - 1 |
284 | 292 |
assert not User.objects.filter(uuid=user_uuid) |
293 | ||
294 | ||
295 |
def test_fedict_authenticator_data_migration(settings): |
|
296 |
app = 'authentic2_auth_fedict' |
|
297 |
migrate_from = [(app, '0001_initial')] |
|
298 |
migrate_to = [(app, '0002_auto_20220706_1712')] |
|
299 | ||
300 |
executor = MigrationExecutor(connection) |
|
301 |
old_apps = executor.loader.project_state(migrate_from).apps |
|
302 |
executor.migrate(migrate_from) |
|
303 |
FedictAuthenticator = old_apps.get_model(app, 'FedictAuthenticator') |
|
304 | ||
305 |
settings.AUTH_FRONTENDS_KWARGS = { |
|
306 |
"fedict": {"priority": 3, "show_condition": "'backoffice' not in login_hint"} |
|
307 |
} |
|
308 |
settings.A2_AUTH_FEDICT_ENABLE = True |
|
309 | ||
310 |
executor = MigrationExecutor(connection) |
|
311 |
executor.migrate(migrate_to) |
|
312 |
executor.loader.build_graph() |
|
313 |
new_apps = executor.loader.project_state(migrate_to).apps |
|
314 |
FedictAuthenticator = new_apps.get_model(app, 'FedictAuthenticator') |
|
315 | ||
316 |
authenticator = FedictAuthenticator.objects.get() |
|
317 |
assert authenticator.slug == 'fedict-authenticator' |
|
318 |
assert authenticator.order == 3 |
|
319 |
assert authenticator.show_condition == "'backoffice' not in login_hint" |
|
320 |
assert authenticator.enabled is True |
|
321 | ||
322 | ||
323 |
def test_manager(app, admin): |
|
324 |
resp = login(app, admin, path='/manage/authenticators/', index=0) |
|
325 | ||
326 |
resp = resp.click('Add new authenticator') |
|
327 |
resp.form['authenticator'] = 'fedict' |
|
328 |
resp = resp.form.submit().follow() |
|
329 | ||
330 |
authenticator = FedictAuthenticator.objects.get() |
|
331 |
assert not authenticator.enabled |
|
332 | ||
333 |
# on edit page, submit |
|
334 |
resp = resp.form.submit().follow() |
|
335 |
resp = resp.click('Enable') |
|
336 | ||
337 |
authenticator.refresh_from_db() |
|
338 |
assert authenticator.enabled |
tests/utils.py | ||
---|---|---|
2 | 2 |
from django.urls import reverse |
3 | 3 | |
4 | 4 | |
5 |
def login(app, user, path=None, password=None, remember_me=None): |
|
5 |
def login(app, user, path=None, password=None, remember_me=None, index=1):
|
|
6 | 6 |
if path: |
7 | 7 |
real_path = make_url(path) |
8 | 8 |
login_page = app.get(real_path, status=302).maybe_follow() |
9 | 9 |
else: |
10 | 10 |
login_page = app.get(reverse('auth_login')) |
11 | 11 |
assert login_page.request.path == reverse('auth_login') |
12 |
form = login_page.forms[1]
|
|
12 |
form = login_page.forms[index]
|
|
13 | 13 |
form.set('username', user.username if hasattr(user, 'username') else user) |
14 | 14 |
# password is supposed to be the same as username |
15 | 15 |
form.set('password', password or user.username) |
16 |
- |