Projet

Général

Profil

0003-auth_oidc-migrate-authenticator-to-database-53902.patch

Valentin Deniaud, 19 mai 2022 13:46

Télécharger (28,2 ko)

Voir les différences:

Subject: [PATCH 3/3] auth_oidc: migrate authenticator to database (#53902)

 src/authentic2/settings.py                    |   1 -
 src/authentic2/views.py                       |   2 +-
 src/authentic2_auth_oidc/authenticators.py    |  64 -----------
 src/authentic2_auth_oidc/forms.py             |  32 ++++++
 .../migrations/0001_initial.py                |   3 +
 ...0009_oidcprovider_baseauthenticator_ptr.py |  19 ++++
 .../migrations/0010_auto_20220413_1622.py     |  43 ++++++++
 .../migrations/0011_auto_20220413_1632.py     |  65 +++++++++++
 src/authentic2_auth_oidc/models.py            |  52 ++++++---
 src/authentic2_auth_oidc/utils.py             |  17 ---
 tests/conftest.py                             |   4 +-
 tests/test_auth_oidc.py                       | 101 +++++++++++-------
 tests/test_commands.py                        |   2 +
 tests/test_manager_authenticators.py          |  70 ++++++++++++
 14 files changed, 339 insertions(+), 136 deletions(-)
 delete mode 100644 src/authentic2_auth_oidc/authenticators.py
 create mode 100644 src/authentic2_auth_oidc/forms.py
 create mode 100644 src/authentic2_auth_oidc/migrations/0009_oidcprovider_baseauthenticator_ptr.py
 create mode 100644 src/authentic2_auth_oidc/migrations/0010_auto_20220413_1622.py
 create mode 100644 src/authentic2_auth_oidc/migrations/0011_auto_20220413_1632.py
src/authentic2/settings.py
189 189
AUTH_USER_MODEL = 'custom_user.User'
190 190
AUTH_FRONTENDS = (
191 191
    'authentic2_auth_saml.authenticators.SAMLAuthenticator',
192
    'authentic2_auth_oidc.authenticators.OIDCAuthenticator',
193 192
    'authentic2_auth_fc.authenticators.FcAuthenticator',
194 193
)
195 194

  
src/authentic2/views.py
444 444
        if hasattr(authenticator, 'autorun'):
445 445
            if 'message' in token:
446 446
                messages.info(request, token['message'])
447
            return authenticator.autorun(request, block['id'])
447
            return authenticator.autorun(request, block.get('id'))
448 448

  
449 449
    # Old frontends API
450 450
    for block in blocks:
src/authentic2_auth_oidc/authenticators.py
1
# authentic2 - versatile identity manager
2
# Copyright (C) 2010-2019 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 django.shortcuts import render
18
from django.utils.translation import gettext_noop
19

  
20
from authentic2.authenticators import BaseAuthenticator
21
from authentic2.utils.misc import make_url, redirect_to_login
22

  
23
from . import app_settings, utils
24
from .models import OIDCProvider
25

  
26

  
27
class OIDCAuthenticator(BaseAuthenticator):
28
    id = 'oidc'
29
    how = ['oidc']
30
    priority = 2
31

  
32
    def enabled(self):
33
        return app_settings.ENABLE and utils.has_providers()
34

  
35
    def name(self):
36
        return gettext_noop('OpenIDConnect')
37

  
38
    def instances(self, request, *args, **kwargs):
39
        for p in utils.get_providers(shown=True):
40
            yield (p.slug, p)
41

  
42
    def autorun(self, request, block_id):
43
        auth_id, instance_slug = block_id.split('_')
44
        assert auth_id == self.id
45

  
46
        try:
47
            provider = OIDCProvider.objects.get(slug=instance_slug)
48
        except OIDCProvider.DoesNotExist():
49
            return redirect_to_login(request)
50
        return redirect_to_login(request, login_url='oidc-login', kwargs={'pk': provider.pk})
51

  
52
    def login(self, request, *args, **kwargs):
53
        context = kwargs.get('context', {})
54
        if kwargs.get('instance'):
55
            instance = kwargs['instance']
56
            context['provider'] = instance
57
            context['login_url'] = make_url(
58
                'oidc-login', kwargs={'pk': instance.id}, request=request, keep_params=True
59
            )
60
            template_names = [
61
                'authentic2_auth_oidc/login_%s.html' % instance.slug,
62
                'authentic2_auth_oidc/login.html',
63
            ]
64
            return render(request, template_names, context)
src/authentic2_auth_oidc/forms.py
1
# authentic2 - versatile identity manager
2
# Copyright (C) 2010-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 django import forms
18

  
19
from authentic2.apps.authenticators.forms import AuthenticatorFormMixin
20

  
21
from .models import OIDCProvider
22

  
23

  
24
class OIDCProviderEditForm(AuthenticatorFormMixin, forms.ModelForm):
25
    class Meta:
26
        model = OIDCProvider
27
        fields = '__all__'
28

  
29
    def __init__(self, *args, **kwargs):
30
        super().__init__(*args, **kwargs)
31
        self.fields['ou'].required = True
32
        self.fields['ou'].empty_label = None
src/authentic2_auth_oidc/migrations/0001_initial.py
123 123
                    ),
124 124
                ),
125 125
            ],
126
            options={
127
                'verbose_name': 'OpenIDConnect',
128
            },
126 129
        ),
127 130
        migrations.AddField(
128 131
            model_name='oidcclaimmapping',
src/authentic2_auth_oidc/migrations/0009_oidcprovider_baseauthenticator_ptr.py
1
# Generated by Django 2.2.28 on 2022-04-13 14:22
2

  
3
from django.db import migrations, models
4

  
5

  
6
class Migration(migrations.Migration):
7

  
8
    dependencies = [
9
        ('authentic2_auth_oidc', '0008_auto_20201102_1142'),
10
    ]
11

  
12
    operations = [
13
        migrations.AddField(
14
            model_name='oidcprovider',
15
            name='baseauthenticator_ptr',
16
            field=models.IntegerField(default=0),
17
            preserve_default=False,
18
        ),
19
    ]
src/authentic2_auth_oidc/migrations/0010_auto_20220413_1622.py
1
# Generated by Django 2.2.28 on 2022-04-13 14:22
2

  
3
from django.db import migrations
4
from django.utils.text import slugify
5

  
6
from authentic2 import app_settings as global_settings
7
from authentic2_auth_oidc import app_settings
8

  
9

  
10
def add_base_authenticators(apps, schema_editor):
11
    kwargs_settings = getattr(global_settings, 'AUTH_FRONTENDS_KWARGS', {})
12
    oidc_provider_settings = kwargs_settings.get('oidc', {})
13
    show_condition = oidc_provider_settings.get('show_condition')
14

  
15
    BaseAuthenticator = apps.get_model('authenticators', 'BaseAuthenticator')
16
    OIDCProvider = apps.get_model('authentic2_auth_oidc', 'OIDCProvider')
17

  
18
    for provider in OIDCProvider.objects.all():
19
        if isinstance(show_condition, dict):
20
            show_condition = show_condition.get(provider.slug, '')
21

  
22
        base_authenticator = BaseAuthenticator.objects.create(
23
            name=provider.name,
24
            slug=provider.slug or slugify(provider.name),
25
            ou=provider.ou,
26
            enabled=provider.show and app_settings.ENABLE,
27
            order=oidc_provider_settings.get('priority', 2),
28
            show_condition=show_condition,
29
        )
30
        provider.baseauthenticator_ptr = base_authenticator.pk
31
        provider.save()
32

  
33

  
34
class Migration(migrations.Migration):
35

  
36
    dependencies = [
37
        ('authentic2_auth_oidc', '0009_oidcprovider_baseauthenticator_ptr'),
38
        ('authenticators', '0001_initial'),
39
    ]
40

  
41
    operations = [
42
        migrations.RunPython(add_base_authenticators, reverse_code=migrations.RunPython.noop),
43
    ]
src/authentic2_auth_oidc/migrations/0011_auto_20220413_1632.py
1
# Generated by Django 2.2.28 on 2022-04-13 14:32
2

  
3
import django.db.models.deletion
4
from django.db import migrations, models
5
from django.db.migrations.operations.base import Operation
6

  
7

  
8
class AlterModelBase(Operation):
9
    def __init__(self, model_name, base_name):
10
        self.model_name = model_name
11
        self.base_name = base_name
12

  
13
    def state_forwards(self, app_label, state):
14
        model_state = state.models[app_label, self.model_name]
15
        model_state.bases = (self.base_name,)
16
        state.reload_model(app_label, self.model_name, delay=True)
17

  
18
    def database_forwards(self, app_label, schema_editor, from_state, to_state):
19
        pass
20

  
21
    def database_backwards(self, app_label, schema_editor, from_state, to_state):
22
        pass
23

  
24

  
25
class Migration(migrations.Migration):
26

  
27
    dependencies = [
28
        ('authentic2_auth_oidc', '0010_auto_20220413_1622'),
29
    ]
30

  
31
    operations = [
32
        migrations.RemoveField(
33
            model_name='oidcprovider',
34
            name='id',
35
        ),
36
        migrations.RemoveField(
37
            model_name='oidcprovider',
38
            name='name',
39
        ),
40
        migrations.RemoveField(
41
            model_name='oidcprovider',
42
            name='ou',
43
        ),
44
        migrations.RemoveField(
45
            model_name='oidcprovider',
46
            name='show',
47
        ),
48
        migrations.RemoveField(
49
            model_name='oidcprovider',
50
            name='slug',
51
        ),
52
        migrations.AlterField(
53
            model_name='oidcprovider',
54
            name='baseauthenticator_ptr',
55
            field=models.OneToOneField(
56
                auto_created=True,
57
                on_delete=django.db.models.deletion.CASCADE,
58
                parent_link=True,
59
                primary_key=True,
60
                serialize=False,
61
                to='authenticators.BaseAuthenticator',
62
            ),
63
        ),
64
        AlterModelBase('oidcprovider', 'authenticators.baseauthenticator'),
65
    ]
src/authentic2_auth_oidc/models.py
21 21
from django.contrib.postgres.fields import JSONField
22 22
from django.core.exceptions import ValidationError
23 23
from django.db import models
24
from django.shortcuts import render
24 25
from django.utils.translation import gettext_lazy as _
25 26
from jwcrypto.jwk import InvalidJWKValue, JWKSet
26 27

  
27
from authentic2.a2_rbac.models import OrganizationalUnit
28
from authentic2.apps.authenticators.models import BaseAuthenticator
29
from authentic2.utils.misc import make_url, redirect_to_login
28 30
from authentic2.utils.template import validate_template
29 31

  
30 32
from . import managers
......
38 40
        raise ValidationError(_('Invalid JWKSet: %s') % e)
39 41

  
40 42

  
41
class OIDCProvider(models.Model):
43
class OIDCProvider(BaseAuthenticator):
42 44
    STRATEGY_CREATE = 'create'
43 45
    STRATEGY_FIND_UUID = 'find-uuid'
44 46
    STRATEGY_FIND_USERNAME = 'find-username'
......
61 63
        (ALGO_EC, _('EC')),
62 64
    ]
63 65

  
64
    name = models.CharField(unique=True, max_length=128, verbose_name=_('name'))
65
    slug = models.SlugField(unique=True, max_length=256, verbose_name=_('slug'), blank=True, null=True)
66 66
    issuer = models.CharField(max_length=256, verbose_name=_('issuer'), unique=True, db_index=True)
67 67
    client_id = models.CharField(max_length=128, default=uuid.uuid4, verbose_name=_('client id'))
68 68
    client_secret = models.CharField(max_length=128, default=uuid.uuid4, verbose_name=_('client secret'))
......
89 89

  
90 90
    # ou where new users should be created
91 91
    strategy = models.CharField(max_length=32, choices=STRATEGIES, verbose_name=_('strategy'))
92
    ou = models.ForeignKey(
93
        to=OrganizationalUnit, verbose_name=_('organizational unit'), on_delete=models.CASCADE
94
    )
95 92

  
96 93
    # policy
97 94
    max_auth_age = models.PositiveIntegerField(
98 95
        verbose_name=_('max authentication age'), blank=True, null=True
99 96
    )
100 97

  
101
    # hide OP from login page
102
    show = models.BooleanField(verbose_name=_('show on login page'), blank=True, default=True)
103

  
104 98
    # metadata
105 99
    created = models.DateTimeField(verbose_name=_('created'), auto_now_add=True)
106 100
    modified = models.DateTimeField(verbose_name=_('modified'), auto_now=True)
107 101

  
108 102
    objects = managers.OIDCProviderManager()
109 103

  
104
    type = 'oidc'
105
    how = ['oidc']
106
    description_fields = ['show_condition', 'issuer', 'scopes', 'strategy', 'created', 'modified']
107

  
108
    class Meta:
109
        verbose_name = _('OpenIDConnect')
110

  
111
    @property
112
    def manager_form_class(self):
113
        from .forms import OIDCProviderEditForm
114

  
115
        return OIDCProviderEditForm
116

  
110 117
    @property
111 118
    def jwkset(self):
112 119
        if self.jwkset_json:
113 120
            return JWKSet.from_json(json.dumps(self.jwkset_json))
114 121
        return None
115 122

  
123
    def get_short_description(self):
124
        if self.issuer and self.scopes:
125
            return _('OIDC provider linked to issuer %(issuer)s with scopes %(scopes)s.') % {
126
                'issuer': self.issuer,
127
                'scopes': self.scopes.replace(' ', ', '),
128
            }
129

  
116 130
    def clean_fields(self, exclude=None):
117 131
        super().clean_fields(exclude=exclude)
118 132
        exclude = exclude or []
......
145 159
                    % key_sig_mapping[self.idtoken_algo]
146 160
                )
147 161

  
148
    def __str__(self):
149
        return str(self.name)
150

  
151 162
    def authorization_claims_parameter(self):
152 163
        idtoken_claims = {}
153 164
        userinfo_claims = {}
......
165 176
    def __repr__(self):
166 177
        return '<OIDCProvider %r>' % self.issuer
167 178

  
179
    def autorun(self, request, *args):
180
        return redirect_to_login(request, login_url='oidc-login', kwargs={'pk': self.pk})
181

  
182
    def login(self, request, *args, **kwargs):
183
        context = kwargs.get('context', {})
184
        context['provider'] = self
185
        context['login_url'] = make_url(
186
            'oidc-login', kwargs={'pk': self.id}, request=request, keep_params=True
187
        )
188
        template_names = [
189
            'authentic2_auth_oidc/login_%s.html' % self.slug,
190
            'authentic2_auth_oidc/login.html',
191
        ]
192
        return render(request, template_names, context)
193

  
168 194

  
169 195
class OIDCClaimMapping(models.Model):
170 196
    NOT_VERIFIED = 0
src/authentic2_auth_oidc/utils.py
29 29
from authentic2.models import Attribute
30 30
from authentic2.utils.cache import GlobalCache
31 31

  
32
from . import models
33

  
34 32
TIMEOUT = 1
35 33

  
36 34

  
37
@GlobalCache(timeout=5, kwargs=['shown'])
38
def get_providers(shown=None):
39
    qs = models.OIDCProvider.objects.all()
40
    if shown is not None:
41
        qs = qs.filter(show=shown)
42
    return qs
43

  
44

  
45 35
@GlobalCache(timeout=TIMEOUT)
46 36
def get_attributes():
47 37
    return Attribute.objects.all()
......
54 44
    return get_object_or_404(models.OIDCProvider, pk=pk)
55 45

  
56 46

  
57
@GlobalCache(timeout=TIMEOUT)
58
def has_providers():
59
    from . import models
60

  
61
    return models.OIDCProvider.objects.filter(show=True).exists()
62

  
63

  
64 47
@GlobalCache(timeout=TIMEOUT)
65 48
def get_provider_by_issuer(issuer):
66 49
    from . import models
tests/conftest.py
34 34
from authentic2.manager.utils import get_ou_count
35 35
from authentic2.models import Attribute, Service
36 36
from authentic2.utils.evaluate import BaseExpressionValidator
37
from authentic2_auth_oidc.utils import get_provider_by_issuer, get_providers, has_providers
37
from authentic2_auth_oidc.utils import get_provider_by_issuer
38 38
from authentic2_idp_oidc.models import OIDCClient
39 39

  
40 40
from . import utils
......
339 339
    for cached_el in (
340 340
        OrganizationalUnit.cached,
341 341
        a2_hooks.get_hooks,
342
        get_providers,
343 342
        get_provider_by_issuer,
344 343
        get_ou_count,
345
        has_providers,
346 344
    ):
347 345
        cached_el.cache.clear()
348 346

  
tests/test_auth_oidc.py
44 44
from authentic2.utils.misc import last_authentication_event
45 45
from authentic2_auth_oidc.backends import OIDCBackend
46 46
from authentic2_auth_oidc.models import OIDCAccount, OIDCClaimMapping, OIDCProvider
47
from authentic2_auth_oidc.utils import (
48
    IDToken,
49
    IDTokenError,
50
    get_providers,
51
    has_providers,
52
    parse_id_token,
53
    register_issuer,
54
)
47
from authentic2_auth_oidc.utils import IDToken, IDTokenError, parse_id_token, register_issuer
55 48

  
56 49
from . import utils
57 50

  
......
176 169
        ou=get_default_ou(),
177 170
        name=name,
178 171
        slug=slug,
172
        enabled=True,
179 173
        issuer=issuer,
180 174
        authorization_endpoint='%s/authorize' % issuer,
181 175
        token_endpoint='%s/token' % issuer,
......
412 406
        ou=get_default_ou(),
413 407
        name='OIDCIDP 2',
414 408
        slug='oidcidp-2',
409
        enabled=True,
415 410
        issuer='https://idp2.example.com/',
416 411
        authorization_endpoint='https://idp2.example.com/authorize',
417 412
        token_endpoint='https://idp2.example.com/token',
......
431 426

  
432 427

  
433 428
def test_login_with_conditional_authenticators(oidc_provider, oidc_provider_jwkset, app, settings, caplog):
434
    make_oidc_provider(name='My IDP', slug='myidp', jwkset=oidc_provider_jwkset)
429
    myidp = make_oidc_provider(name='My IDP', slug='myidp', jwkset=oidc_provider_jwkset)
435 430
    response = app.get('/login/')
436 431
    assert 'My IDP' in response
437 432
    assert 'Server' in response
438 433

  
439
    settings.AUTH_FRONTENDS_KWARGS = {'oidc': {'show_condition': {'myidp': 'remote_addr==\'0.0.0.0\''}}}
434
    myidp.show_condition = 'remote_addr==\'0.0.0.0\''
435
    myidp.save()
440 436
    response = app.get('/login/')
441 437
    assert 'Server' in response
442 438
    assert 'My IDP' not in response
443 439

  
444
    settings.AUTH_FRONTENDS_KWARGS = {
445
        'oidc': {
446
            'show_condition': {'myid': 'remote_addr==\'0.0.0.0\'', 'server': 'remote_addr==\'127.0.0.1\''}
447
        }
448
    }
449
    response = app.get('/login/')
450
    assert 'Server' in response
451
    assert 'My IDP' in response
452

  
453
    settings.AUTH_FRONTENDS_KWARGS = {
454
        'oidc': {
455
            'show_condition': {'myidp': 'remote_addr==\'0.0.0.0\'', 'server': 'remote_addr==\'127.0.0.1\''}
456
        }
457
    }
440
    oidc_provider.show_condition = 'remote_addr==\'127.0.0.1\''
441
    oidc_provider.save()
458 442
    response = app.get('/login/')
459 443
    assert 'Server' in response
460 444
    assert 'My IDP' not in response
461 445

  
462
    settings.AUTH_FRONTENDS_KWARGS = {'oidc': {'show_condition': 'remote_addr==\'127.0.0.1\''}}
446
    myidp.show_condition = 'remote_addr==\'127.0.0.1\''
447
    myidp.save()
463 448
    response = app.get('/login/')
464 449
    assert 'Server' in response
465 450
    assert 'My IDP' in response
466 451

  
467
    settings.AUTH_FRONTENDS_KWARGS = {
468
        'oidc': {
469
            'show_condition': {
470
                'myidp': 'remote_addr==\'127.0.0.1\' and \'backoffice\' not in login_hint',
471
                'server': '\'backoffice\' in login_hint',
472
            }
473
        }
474
    }
452
    myidp.show_condition = 'remote_addr==\'127.0.0.1\' and \'backoffice\' not in login_hint'
453
    myidp.save()
454
    oidc_provider.show_condition = '\'backoffice\' in login_hint'
455
    oidc_provider.save()
475 456
    response = app.get('/login/')
476 457
    assert 'Server' not in response
477 458
    assert 'My IDP' in response
......
631 612
    assert 'oidc-a-server' in response.text
632 613

  
633 614
    # do not show this provider on login page anymore
634
    oidc_provider.show = False
615
    oidc_provider.enabled = False
635 616
    oidc_provider.save()
636 617

  
637
    # we have a 5 seconds cache on list of providers, we have to work around it
638
    get_providers.cache.clear()
639
    has_providers.cache.clear()
640 618
    response = app.get('/login/')
641 619
    assert 'oidc-a-server' not in response.text
642 620

  
......
1091 1069
        ):
1092 1070
            response = app.get(login_callback_url(oidc_provider), params={'code': code, 'state': state})
1093 1071
        assert User.objects.count() == 1
1072

  
1073

  
1074
@pytest.mark.parametrize(
1075
    'auth_frontend_kwargs',
1076
    [
1077
        {'oidc': {'priority': 3, 'show_condition': '"backoffice" not in login_hint'}},
1078
        {'oidc': {'show_condition': {'baz': '"backoffice" not in login_hint', 'bar': 'True'}}},
1079
    ],
1080
)
1081
def test_oidc_provider_authenticator_data_migration(auth_frontend_kwargs, migration, settings):
1082
    settings.AUTH_FRONTENDS_KWARGS = auth_frontend_kwargs
1083

  
1084
    app = 'authentic2_auth_oidc'
1085
    migrate_from = [(app, '0008_auto_20201102_1142')]
1086
    migrate_to = [(app, '0011_auto_20220413_1632')]
1087

  
1088
    old_apps = migration.before(migrate_from)
1089
    OIDCProvider = old_apps.get_model(app, 'OIDCProvider')
1090
    OrganizationalUnit = old_apps.get_model('a2_rbac', 'OrganizationalUnit')
1091
    ou1 = OrganizationalUnit.objects.create(name='OU1', slug='ou1')
1092
    issuer = 'https://baz.example.com'
1093
    OIDCProvider.objects.create(
1094
        name='Baz',
1095
        slug='baz',
1096
        ou=ou1,
1097
        show=True,
1098
        issuer=issuer,
1099
        authorization_endpoint='%s/authorize' % issuer,
1100
        token_endpoint='%s/token' % issuer,
1101
        end_session_endpoint='%s/logout' % issuer,
1102
        userinfo_endpoint='%s/user_info' % issuer,
1103
        token_revocation_endpoint='%s/revoke' % issuer,
1104
    )
1105

  
1106
    new_apps = migration.apply(migrate_to)
1107
    OIDCProvider = new_apps.get_model(app, 'OIDCProvider')
1108
    BaseAuthenticator = new_apps.get_model('authenticators', 'BaseAuthenticator')
1109

  
1110
    authenticator = OIDCProvider.objects.get()
1111
    assert authenticator.name == 'Baz'
1112
    assert authenticator.slug == 'baz'
1113
    assert authenticator.ou.pk == ou1.pk
1114
    assert authenticator.enabled is True
1115
    assert authenticator.order == auth_frontend_kwargs['oidc'].get('priority', 2)
1116
    assert authenticator.show_condition == '"backoffice" not in login_hint'
1117
    assert authenticator.authorization_endpoint == '%s/authorize' % issuer
1118

  
1119
    base_authenticator = BaseAuthenticator.objects.get()
1120
    assert authenticator.uuid == base_authenticator.uuid
tests/test_commands.py
263 263
        jwkset.add(JWK.generate(kty='EC', size=256, kid='tsrn'))
264 264
        return OIDCProvider.objects.create(
265 265
            name=name,
266
            slug='test',
267
            enabled=True,
266 268
            ou=ou,
267 269
            issuer=issuer,
268 270
            strategy='create',
tests/test_manager_authenticators.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
import pytest
18

  
19
from authentic2_auth_oidc.models import OIDCProvider
20

  
17 21
from .utils import login, logout
18 22

  
19 23

  
......
74 78
    # cannot add another password authenticator
75 79
    resp = app.get('/manage/authenticators/add/')
76 80
    assert 'Password' not in resp.text
81

  
82

  
83
@pytest.mark.freeze_time('2022-04-19 14:00')
84
def test_authenticators_oidc(app, superuser, ou1, ou2):
85
    resp = login(app, superuser, path='/manage/authenticators/')
86

  
87
    resp = resp.click('Add new authenticator')
88
    resp.form['name'] = 'Test'
89
    resp.form['authenticator'] = 'oidc'
90
    resp.form['ou'] = ou1.pk
91

  
92
    resp = resp.form.submit().follow()
93
    assert OIDCProvider.objects.filter(slug='test').count() == 1
94
    assert 'Created: April 19, 2022, 2 p.m.' in resp.text
95
    assert 'Modified: April 19, 2022, 2 p.m.' in resp.text
96
    assert 'Issuer' not in resp.text
97

  
98
    resp = resp.click('Edit')
99
    assert 'enabled' not in resp.form.fields
100
    resp.form['issuer'] = 'https://oidc.example.com'
101
    resp.form['scopes'] = 'profile email'
102
    resp.form['strategy'] = 'create'
103
    resp.form['authorization_endpoint'] = 'https://oidc.example.com/authorize'
104
    resp.form['token_endpoint'] = 'https://oidc.example.com/token'
105
    resp.form['userinfo_endpoint'] = 'https://oidc.example.com/user_info'
106
    resp.form['idtoken_algo'] = 2
107
    resp = resp.form.submit().follow()
108

  
109
    assert 'Issuer: https://oidc.example.com' in resp.text
110
    assert 'Scopes: profile email' in resp.text
111

  
112
    resp = app.get('/manage/authenticators/')
113
    assert 'OpenIDConnect - Test' in resp.text
114
    assert 'class="section disabled"' in resp.text
115
    assert 'OIDC provider linked to' not in resp.text
116

  
117
    resp = resp.click('Configure', index=1)
118
    resp = resp.click('Enable').follow()
119
    assert 'Authenticator has been enabled.' in resp.text
120

  
121
    resp = app.get('/manage/authenticators/')
122
    assert 'class="section disabled"' not in resp.text
123
    assert 'OIDC provider linked to https://oidc.example.com with scopes profile, email.' not in resp.text
124

  
125
    # same name
126
    resp = resp.click('Add new authenticator')
127
    resp.form['name'] = 'test'
128
    resp.form['authenticator'] = 'oidc'
129
    resp.form['ou'] = ou1.pk
130
    resp = resp.form.submit().follow()
131
    assert OIDCProvider.objects.filter(slug='test-1').count() == 1
132
    OIDCProvider.objects.filter(slug='test-1').delete()
133

  
134
    # OU is required
135
    resp = app.get('/manage/authenticators/add/')
136
    resp.form['name'] = 'test'
137
    resp.form['authenticator'] = 'oidc'
138
    resp.form['ou'] = ''
139
    resp = resp.form.submit()
140
    assert 'This field is required' in resp.text
141

  
142
    resp = app.get('/manage/authenticators/')
143
    resp = resp.click('Configure', index=1)
144
    resp = resp.click('Delete')
145
    resp = resp.form.submit().follow()
146
    assert not OIDCProvider.objects.filter(slug='test').exists()
77
-