Projet

Général

Profil

0002-migrate-authenticator-to-database-66876.patch

Valentin Deniaud, 07 juillet 2022 14:01

Télécharger (14,4 ko)

Voir les différences:

Subject: [PATCH 2/2] migrate authenticator to database (#66876)

 src/authentic2_auth_fedict/__init__.py        |  6 --
 src/authentic2_auth_fedict/app_settings.py    | 44 --------------
 src/authentic2_auth_fedict/forms.py           | 26 +++++++++
 .../migrations/0001_initial.py                | 36 ++++++++++++
 .../migrations/0002_auto_20220706_1712.py     | 35 +++++++++++
 .../migrations/__init__.py                    |  0
 src/authentic2_auth_fedict/models.py          | 19 +++---
 src/authentic2_auth_fedict/signals.py         |  4 +-
 tests/settings.py                             |  1 -
 tests/test_all.py                             | 58 ++++++++++++++++++-
 tests/utils.py                                |  4 +-
 11 files changed, 167 insertions(+), 66 deletions(-)
 delete mode 100644 src/authentic2_auth_fedict/app_settings.py
 create mode 100644 src/authentic2_auth_fedict/forms.py
 create mode 100644 src/authentic2_auth_fedict/migrations/0001_initial.py
 create mode 100644 src/authentic2_auth_fedict/migrations/0002_auto_20220706_1712.py
 create mode 100644 src/authentic2_auth_fedict/migrations/__init__.py
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
-