auth_oidc: migrate authenticator to database (#53902)
This commit is contained in:
parent
46c99d7816
commit
2c6b3d2e3a
|
@ -189,7 +189,6 @@ ACCOUNT_ACTIVATION_DAYS = 2
|
|||
AUTH_USER_MODEL = 'custom_user.User'
|
||||
AUTH_FRONTENDS = (
|
||||
'authentic2_auth_saml.authenticators.SAMLAuthenticator',
|
||||
'authentic2_auth_oidc.authenticators.OIDCAuthenticator',
|
||||
'authentic2_auth_fc.authenticators.FcAuthenticator',
|
||||
)
|
||||
|
||||
|
|
|
@ -444,7 +444,7 @@ def login(request, template_name='authentic2/login.html', redirect_field_name=RE
|
|||
if hasattr(authenticator, 'autorun'):
|
||||
if 'message' in token:
|
||||
messages.info(request, token['message'])
|
||||
return authenticator.autorun(request, block['id'])
|
||||
return authenticator.autorun(request, block.get('id'))
|
||||
|
||||
# Old frontends API
|
||||
for block in blocks:
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
# authentic2 - versatile identity manager
|
||||
# Copyright (C) 2010-2019 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import gettext_noop
|
||||
|
||||
from authentic2.authenticators import BaseAuthenticator
|
||||
from authentic2.utils.misc import make_url, redirect_to_login
|
||||
|
||||
from . import app_settings, utils
|
||||
from .models import OIDCProvider
|
||||
|
||||
|
||||
class OIDCAuthenticator(BaseAuthenticator):
|
||||
id = 'oidc'
|
||||
how = ['oidc']
|
||||
priority = 2
|
||||
|
||||
def enabled(self):
|
||||
return app_settings.ENABLE and utils.has_providers()
|
||||
|
||||
def name(self):
|
||||
return gettext_noop('OpenIDConnect')
|
||||
|
||||
def instances(self, request, *args, **kwargs):
|
||||
for p in utils.get_providers(shown=True):
|
||||
yield (p.slug, p)
|
||||
|
||||
def autorun(self, request, block_id):
|
||||
auth_id, instance_slug = block_id.split('_')
|
||||
assert auth_id == self.id
|
||||
|
||||
try:
|
||||
provider = OIDCProvider.objects.get(slug=instance_slug)
|
||||
except OIDCProvider.DoesNotExist():
|
||||
return redirect_to_login(request)
|
||||
return redirect_to_login(request, login_url='oidc-login', kwargs={'pk': provider.pk})
|
||||
|
||||
def login(self, request, *args, **kwargs):
|
||||
context = kwargs.get('context', {})
|
||||
if kwargs.get('instance'):
|
||||
instance = kwargs['instance']
|
||||
context['provider'] = instance
|
||||
context['login_url'] = make_url(
|
||||
'oidc-login', kwargs={'pk': instance.id}, request=request, keep_params=True
|
||||
)
|
||||
template_names = [
|
||||
'authentic2_auth_oidc/login_%s.html' % instance.slug,
|
||||
'authentic2_auth_oidc/login.html',
|
||||
]
|
||||
return render(request, template_names, context)
|
|
@ -0,0 +1,32 @@
|
|||
# authentic2 - versatile identity manager
|
||||
# Copyright (C) 2010-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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django import forms
|
||||
|
||||
from authentic2.apps.authenticators.forms import AuthenticatorFormMixin
|
||||
|
||||
from .models import OIDCProvider
|
||||
|
||||
|
||||
class OIDCProviderEditForm(AuthenticatorFormMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = OIDCProvider
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['ou'].required = True
|
||||
self.fields['ou'].empty_label = None
|
|
@ -123,6 +123,9 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'OpenIDConnect',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oidcclaimmapping',
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 2.2.28 on 2022-04-13 14:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentic2_auth_oidc', '0008_auto_20201102_1142'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='oidcprovider',
|
||||
name='baseauthenticator_ptr',
|
||||
field=models.IntegerField(default=0),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
|
@ -0,0 +1,43 @@
|
|||
# Generated by Django 2.2.28 on 2022-04-13 14:22
|
||||
|
||||
from django.db import migrations
|
||||
from django.utils.text import slugify
|
||||
|
||||
from authentic2 import app_settings as global_settings
|
||||
from authentic2_auth_oidc import app_settings
|
||||
|
||||
|
||||
def add_base_authenticators(apps, schema_editor):
|
||||
kwargs_settings = getattr(global_settings, 'AUTH_FRONTENDS_KWARGS', {})
|
||||
oidc_provider_settings = kwargs_settings.get('oidc', {})
|
||||
show_condition = oidc_provider_settings.get('show_condition')
|
||||
|
||||
BaseAuthenticator = apps.get_model('authenticators', 'BaseAuthenticator')
|
||||
OIDCProvider = apps.get_model('authentic2_auth_oidc', 'OIDCProvider')
|
||||
|
||||
for provider in OIDCProvider.objects.all():
|
||||
if isinstance(show_condition, dict):
|
||||
show_condition = show_condition.get(provider.slug, '')
|
||||
|
||||
base_authenticator = BaseAuthenticator.objects.create(
|
||||
name=provider.name,
|
||||
slug=provider.slug or slugify(provider.name),
|
||||
ou=provider.ou,
|
||||
enabled=provider.show and app_settings.ENABLE,
|
||||
order=oidc_provider_settings.get('priority', 2),
|
||||
show_condition=show_condition,
|
||||
)
|
||||
provider.baseauthenticator_ptr = base_authenticator.pk
|
||||
provider.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentic2_auth_oidc', '0009_oidcprovider_baseauthenticator_ptr'),
|
||||
('authenticators', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_base_authenticators, reverse_code=migrations.RunPython.noop),
|
||||
]
|
|
@ -0,0 +1,65 @@
|
|||
# Generated by Django 2.2.28 on 2022-04-13 14:32
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
from django.db.migrations.operations.base import Operation
|
||||
|
||||
|
||||
class AlterModelBase(Operation):
|
||||
def __init__(self, model_name, base_name):
|
||||
self.model_name = model_name
|
||||
self.base_name = base_name
|
||||
|
||||
def state_forwards(self, app_label, state):
|
||||
model_state = state.models[app_label, self.model_name]
|
||||
model_state.bases = (self.base_name,)
|
||||
state.reload_model(app_label, self.model_name, delay=True)
|
||||
|
||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||
pass
|
||||
|
||||
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentic2_auth_oidc', '0010_auto_20220413_1622'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='oidcprovider',
|
||||
name='id',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='oidcprovider',
|
||||
name='name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='oidcprovider',
|
||||
name='ou',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='oidcprovider',
|
||||
name='show',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='oidcprovider',
|
||||
name='slug',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='oidcprovider',
|
||||
name='baseauthenticator_ptr',
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to='authenticators.BaseAuthenticator',
|
||||
),
|
||||
),
|
||||
AlterModelBase('oidcprovider', 'authenticators.baseauthenticator'),
|
||||
]
|
|
@ -21,10 +21,12 @@ from django.conf import settings
|
|||
from django.contrib.postgres.fields import JSONField
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from jwcrypto.jwk import InvalidJWKValue, JWKSet
|
||||
|
||||
from authentic2.a2_rbac.models import OrganizationalUnit
|
||||
from authentic2.apps.authenticators.models import BaseAuthenticator
|
||||
from authentic2.utils.misc import make_url, redirect_to_login
|
||||
from authentic2.utils.template import validate_template
|
||||
|
||||
from . import managers
|
||||
|
@ -38,7 +40,7 @@ def validate_jwkset(data):
|
|||
raise ValidationError(_('Invalid JWKSet: %s') % e)
|
||||
|
||||
|
||||
class OIDCProvider(models.Model):
|
||||
class OIDCProvider(BaseAuthenticator):
|
||||
STRATEGY_CREATE = 'create'
|
||||
STRATEGY_FIND_UUID = 'find-uuid'
|
||||
STRATEGY_FIND_USERNAME = 'find-username'
|
||||
|
@ -61,8 +63,6 @@ class OIDCProvider(models.Model):
|
|||
(ALGO_EC, _('EC')),
|
||||
]
|
||||
|
||||
name = models.CharField(unique=True, max_length=128, verbose_name=_('name'))
|
||||
slug = models.SlugField(unique=True, max_length=256, verbose_name=_('slug'), blank=True, null=True)
|
||||
issuer = models.CharField(max_length=256, verbose_name=_('issuer'), unique=True, db_index=True)
|
||||
client_id = models.CharField(max_length=128, default=uuid.uuid4, verbose_name=_('client id'))
|
||||
client_secret = models.CharField(max_length=128, default=uuid.uuid4, verbose_name=_('client secret'))
|
||||
|
@ -89,30 +89,44 @@ class OIDCProvider(models.Model):
|
|||
|
||||
# ou where new users should be created
|
||||
strategy = models.CharField(max_length=32, choices=STRATEGIES, verbose_name=_('strategy'))
|
||||
ou = models.ForeignKey(
|
||||
to=OrganizationalUnit, verbose_name=_('organizational unit'), on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
# policy
|
||||
max_auth_age = models.PositiveIntegerField(
|
||||
verbose_name=_('max authentication age'), blank=True, null=True
|
||||
)
|
||||
|
||||
# hide OP from login page
|
||||
show = models.BooleanField(verbose_name=_('show on login page'), blank=True, default=True)
|
||||
|
||||
# metadata
|
||||
created = models.DateTimeField(verbose_name=_('created'), auto_now_add=True)
|
||||
modified = models.DateTimeField(verbose_name=_('modified'), auto_now=True)
|
||||
|
||||
objects = managers.OIDCProviderManager()
|
||||
|
||||
type = 'oidc'
|
||||
how = ['oidc']
|
||||
description_fields = ['show_condition', 'issuer', 'scopes', 'strategy', 'created', 'modified']
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('OpenIDConnect')
|
||||
|
||||
@property
|
||||
def manager_form_class(self):
|
||||
from .forms import OIDCProviderEditForm
|
||||
|
||||
return OIDCProviderEditForm
|
||||
|
||||
@property
|
||||
def jwkset(self):
|
||||
if self.jwkset_json:
|
||||
return JWKSet.from_json(json.dumps(self.jwkset_json))
|
||||
return None
|
||||
|
||||
def get_short_description(self):
|
||||
if self.issuer and self.scopes:
|
||||
return _('OIDC provider linked to issuer %(issuer)s with scopes %(scopes)s.') % {
|
||||
'issuer': self.issuer,
|
||||
'scopes': self.scopes.replace(' ', ', '),
|
||||
}
|
||||
|
||||
def clean_fields(self, exclude=None):
|
||||
super().clean_fields(exclude=exclude)
|
||||
exclude = exclude or []
|
||||
|
@ -145,9 +159,6 @@ class OIDCProvider(models.Model):
|
|||
% key_sig_mapping[self.idtoken_algo]
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
def authorization_claims_parameter(self):
|
||||
idtoken_claims = {}
|
||||
userinfo_claims = {}
|
||||
|
@ -165,6 +176,21 @@ class OIDCProvider(models.Model):
|
|||
def __repr__(self):
|
||||
return '<OIDCProvider %r>' % self.issuer
|
||||
|
||||
def autorun(self, request, *args):
|
||||
return redirect_to_login(request, login_url='oidc-login', kwargs={'pk': self.pk})
|
||||
|
||||
def login(self, request, *args, **kwargs):
|
||||
context = kwargs.get('context', {})
|
||||
context['provider'] = self
|
||||
context['login_url'] = make_url(
|
||||
'oidc-login', kwargs={'pk': self.id}, request=request, keep_params=True
|
||||
)
|
||||
template_names = [
|
||||
'authentic2_auth_oidc/login_%s.html' % self.slug,
|
||||
'authentic2_auth_oidc/login.html',
|
||||
]
|
||||
return render(request, template_names, context)
|
||||
|
||||
|
||||
class OIDCClaimMapping(models.Model):
|
||||
NOT_VERIFIED = 0
|
||||
|
|
|
@ -29,19 +29,9 @@ from authentic2.a2_rbac.utils import get_default_ou
|
|||
from authentic2.models import Attribute
|
||||
from authentic2.utils.cache import GlobalCache
|
||||
|
||||
from . import models
|
||||
|
||||
TIMEOUT = 1
|
||||
|
||||
|
||||
@GlobalCache(timeout=5, kwargs=['shown'])
|
||||
def get_providers(shown=None):
|
||||
qs = models.OIDCProvider.objects.all()
|
||||
if shown is not None:
|
||||
qs = qs.filter(show=shown)
|
||||
return qs
|
||||
|
||||
|
||||
@GlobalCache(timeout=TIMEOUT)
|
||||
def get_attributes():
|
||||
return Attribute.objects.all()
|
||||
|
@ -54,13 +44,6 @@ def get_provider(pk):
|
|||
return get_object_or_404(models.OIDCProvider, pk=pk)
|
||||
|
||||
|
||||
@GlobalCache(timeout=TIMEOUT)
|
||||
def has_providers():
|
||||
from . import models
|
||||
|
||||
return models.OIDCProvider.objects.filter(show=True).exists()
|
||||
|
||||
|
||||
@GlobalCache(timeout=TIMEOUT)
|
||||
def get_provider_by_issuer(issuer):
|
||||
from . import models
|
||||
|
|
|
@ -34,7 +34,7 @@ from authentic2.authentication import OIDCUser
|
|||
from authentic2.manager.utils import get_ou_count
|
||||
from authentic2.models import Attribute, Service
|
||||
from authentic2.utils.evaluate import BaseExpressionValidator
|
||||
from authentic2_auth_oidc.utils import get_provider_by_issuer, get_providers, has_providers
|
||||
from authentic2_auth_oidc.utils import get_provider_by_issuer
|
||||
from authentic2_idp_oidc.models import OIDCClient
|
||||
|
||||
from . import utils
|
||||
|
@ -339,10 +339,8 @@ def clear_cache():
|
|||
for cached_el in (
|
||||
OrganizationalUnit.cached,
|
||||
a2_hooks.get_hooks,
|
||||
get_providers,
|
||||
get_provider_by_issuer,
|
||||
get_ou_count,
|
||||
has_providers,
|
||||
):
|
||||
cached_el.cache.clear()
|
||||
|
||||
|
|
|
@ -44,14 +44,7 @@ from authentic2.models import Attribute, AttributeValue
|
|||
from authentic2.utils.misc import last_authentication_event
|
||||
from authentic2_auth_oidc.backends import OIDCBackend
|
||||
from authentic2_auth_oidc.models import OIDCAccount, OIDCClaimMapping, OIDCProvider
|
||||
from authentic2_auth_oidc.utils import (
|
||||
IDToken,
|
||||
IDTokenError,
|
||||
get_providers,
|
||||
has_providers,
|
||||
parse_id_token,
|
||||
register_issuer,
|
||||
)
|
||||
from authentic2_auth_oidc.utils import IDToken, IDTokenError, parse_id_token, register_issuer
|
||||
|
||||
from . import utils
|
||||
|
||||
|
@ -176,6 +169,7 @@ def make_oidc_provider(
|
|||
ou=get_default_ou(),
|
||||
name=name,
|
||||
slug=slug,
|
||||
enabled=True,
|
||||
issuer=issuer,
|
||||
authorization_endpoint='%s/authorize' % issuer,
|
||||
token_endpoint='%s/token' % issuer,
|
||||
|
@ -412,6 +406,7 @@ def test_providers_on_login_page(oidc_provider, app):
|
|||
ou=get_default_ou(),
|
||||
name='OIDCIDP 2',
|
||||
slug='oidcidp-2',
|
||||
enabled=True,
|
||||
issuer='https://idp2.example.com/',
|
||||
authorization_endpoint='https://idp2.example.com/authorize',
|
||||
token_endpoint='https://idp2.example.com/token',
|
||||
|
@ -431,47 +426,33 @@ def test_providers_on_login_page(oidc_provider, app):
|
|||
|
||||
|
||||
def test_login_with_conditional_authenticators(oidc_provider, oidc_provider_jwkset, app, settings, caplog):
|
||||
make_oidc_provider(name='My IDP', slug='myidp', jwkset=oidc_provider_jwkset)
|
||||
myidp = make_oidc_provider(name='My IDP', slug='myidp', jwkset=oidc_provider_jwkset)
|
||||
response = app.get('/login/')
|
||||
assert 'My IDP' in response
|
||||
assert 'Server' in response
|
||||
|
||||
settings.AUTH_FRONTENDS_KWARGS = {'oidc': {'show_condition': {'myidp': 'remote_addr==\'0.0.0.0\''}}}
|
||||
myidp.show_condition = 'remote_addr==\'0.0.0.0\''
|
||||
myidp.save()
|
||||
response = app.get('/login/')
|
||||
assert 'Server' in response
|
||||
assert 'My IDP' not in response
|
||||
|
||||
settings.AUTH_FRONTENDS_KWARGS = {
|
||||
'oidc': {
|
||||
'show_condition': {'myid': 'remote_addr==\'0.0.0.0\'', 'server': 'remote_addr==\'127.0.0.1\''}
|
||||
}
|
||||
}
|
||||
response = app.get('/login/')
|
||||
assert 'Server' in response
|
||||
assert 'My IDP' in response
|
||||
|
||||
settings.AUTH_FRONTENDS_KWARGS = {
|
||||
'oidc': {
|
||||
'show_condition': {'myidp': 'remote_addr==\'0.0.0.0\'', 'server': 'remote_addr==\'127.0.0.1\''}
|
||||
}
|
||||
}
|
||||
oidc_provider.show_condition = 'remote_addr==\'127.0.0.1\''
|
||||
oidc_provider.save()
|
||||
response = app.get('/login/')
|
||||
assert 'Server' in response
|
||||
assert 'My IDP' not in response
|
||||
|
||||
settings.AUTH_FRONTENDS_KWARGS = {'oidc': {'show_condition': 'remote_addr==\'127.0.0.1\''}}
|
||||
myidp.show_condition = 'remote_addr==\'127.0.0.1\''
|
||||
myidp.save()
|
||||
response = app.get('/login/')
|
||||
assert 'Server' in response
|
||||
assert 'My IDP' in response
|
||||
|
||||
settings.AUTH_FRONTENDS_KWARGS = {
|
||||
'oidc': {
|
||||
'show_condition': {
|
||||
'myidp': 'remote_addr==\'127.0.0.1\' and \'backoffice\' not in login_hint',
|
||||
'server': '\'backoffice\' in login_hint',
|
||||
}
|
||||
}
|
||||
}
|
||||
myidp.show_condition = 'remote_addr==\'127.0.0.1\' and \'backoffice\' not in login_hint'
|
||||
myidp.save()
|
||||
oidc_provider.show_condition = '\'backoffice\' in login_hint'
|
||||
oidc_provider.save()
|
||||
response = app.get('/login/')
|
||||
assert 'Server' not in response
|
||||
assert 'My IDP' in response
|
||||
|
@ -631,12 +612,9 @@ def test_show_on_login_page(app, oidc_provider):
|
|||
assert 'oidc-a-server' in response.text
|
||||
|
||||
# do not show this provider on login page anymore
|
||||
oidc_provider.show = False
|
||||
oidc_provider.enabled = False
|
||||
oidc_provider.save()
|
||||
|
||||
# we have a 5 seconds cache on list of providers, we have to work around it
|
||||
get_providers.cache.clear()
|
||||
has_providers.cache.clear()
|
||||
response = app.get('/login/')
|
||||
assert 'oidc-a-server' not in response.text
|
||||
|
||||
|
@ -1091,3 +1069,52 @@ def test_auth_time_is_null(app, caplog, code, oidc_provider, oidc_provider_jwkse
|
|||
):
|
||||
response = app.get(login_callback_url(oidc_provider), params={'code': code, 'state': state})
|
||||
assert User.objects.count() == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'auth_frontend_kwargs',
|
||||
[
|
||||
{'oidc': {'priority': 3, 'show_condition': '"backoffice" not in login_hint'}},
|
||||
{'oidc': {'show_condition': {'baz': '"backoffice" not in login_hint', 'bar': 'True'}}},
|
||||
],
|
||||
)
|
||||
def test_oidc_provider_authenticator_data_migration(auth_frontend_kwargs, migration, settings):
|
||||
settings.AUTH_FRONTENDS_KWARGS = auth_frontend_kwargs
|
||||
|
||||
app = 'authentic2_auth_oidc'
|
||||
migrate_from = [(app, '0008_auto_20201102_1142')]
|
||||
migrate_to = [(app, '0011_auto_20220413_1632')]
|
||||
|
||||
old_apps = migration.before(migrate_from)
|
||||
OIDCProvider = old_apps.get_model(app, 'OIDCProvider')
|
||||
OrganizationalUnit = old_apps.get_model('a2_rbac', 'OrganizationalUnit')
|
||||
ou1 = OrganizationalUnit.objects.create(name='OU1', slug='ou1')
|
||||
issuer = 'https://baz.example.com'
|
||||
OIDCProvider.objects.create(
|
||||
name='Baz',
|
||||
slug='baz',
|
||||
ou=ou1,
|
||||
show=True,
|
||||
issuer=issuer,
|
||||
authorization_endpoint='%s/authorize' % issuer,
|
||||
token_endpoint='%s/token' % issuer,
|
||||
end_session_endpoint='%s/logout' % issuer,
|
||||
userinfo_endpoint='%s/user_info' % issuer,
|
||||
token_revocation_endpoint='%s/revoke' % issuer,
|
||||
)
|
||||
|
||||
new_apps = migration.apply(migrate_to)
|
||||
OIDCProvider = new_apps.get_model(app, 'OIDCProvider')
|
||||
BaseAuthenticator = new_apps.get_model('authenticators', 'BaseAuthenticator')
|
||||
|
||||
authenticator = OIDCProvider.objects.get()
|
||||
assert authenticator.name == 'Baz'
|
||||
assert authenticator.slug == 'baz'
|
||||
assert authenticator.ou.pk == ou1.pk
|
||||
assert authenticator.enabled is True
|
||||
assert authenticator.order == auth_frontend_kwargs['oidc'].get('priority', 2)
|
||||
assert authenticator.show_condition == '"backoffice" not in login_hint'
|
||||
assert authenticator.authorization_endpoint == '%s/authorize' % issuer
|
||||
|
||||
base_authenticator = BaseAuthenticator.objects.get()
|
||||
assert authenticator.uuid == base_authenticator.uuid
|
||||
|
|
|
@ -263,6 +263,8 @@ def test_oidc_register_issuer(db, tmpdir, monkeypatch):
|
|||
jwkset.add(JWK.generate(kty='EC', size=256, kid='tsrn'))
|
||||
return OIDCProvider.objects.create(
|
||||
name=name,
|
||||
slug='test',
|
||||
enabled=True,
|
||||
ou=ou,
|
||||
issuer=issuer,
|
||||
strategy='create',
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
|
||||
from authentic2_auth_oidc.models import OIDCProvider
|
||||
|
||||
from .utils import login, logout
|
||||
|
||||
|
||||
|
@ -74,3 +78,69 @@ def test_authenticators_password(app, superuser):
|
|||
# cannot add another password authenticator
|
||||
resp = app.get('/manage/authenticators/add/')
|
||||
assert 'Password' not in resp.text
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2022-04-19 14:00')
|
||||
def test_authenticators_oidc(app, superuser, ou1, ou2):
|
||||
resp = login(app, superuser, path='/manage/authenticators/')
|
||||
|
||||
resp = resp.click('Add new authenticator')
|
||||
resp.form['name'] = 'Test'
|
||||
resp.form['authenticator'] = 'oidc'
|
||||
resp.form['ou'] = ou1.pk
|
||||
|
||||
resp = resp.form.submit().follow()
|
||||
assert OIDCProvider.objects.filter(slug='test').count() == 1
|
||||
assert 'Created: April 19, 2022, 2 p.m.' in resp.text
|
||||
assert 'Modified: April 19, 2022, 2 p.m.' in resp.text
|
||||
assert 'Issuer' not in resp.text
|
||||
|
||||
resp = resp.click('Edit')
|
||||
assert 'enabled' not in resp.form.fields
|
||||
resp.form['issuer'] = 'https://oidc.example.com'
|
||||
resp.form['scopes'] = 'profile email'
|
||||
resp.form['strategy'] = 'create'
|
||||
resp.form['authorization_endpoint'] = 'https://oidc.example.com/authorize'
|
||||
resp.form['token_endpoint'] = 'https://oidc.example.com/token'
|
||||
resp.form['userinfo_endpoint'] = 'https://oidc.example.com/user_info'
|
||||
resp.form['idtoken_algo'] = 2
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'Issuer: https://oidc.example.com' in resp.text
|
||||
assert 'Scopes: profile email' in resp.text
|
||||
|
||||
resp = app.get('/manage/authenticators/')
|
||||
assert 'OpenIDConnect - Test' in resp.text
|
||||
assert 'class="section disabled"' in resp.text
|
||||
assert 'OIDC provider linked to' not in resp.text
|
||||
|
||||
resp = resp.click('Configure', index=1)
|
||||
resp = resp.click('Enable').follow()
|
||||
assert 'Authenticator has been enabled.' in resp.text
|
||||
|
||||
resp = app.get('/manage/authenticators/')
|
||||
assert 'class="section disabled"' not in resp.text
|
||||
assert 'OIDC provider linked to https://oidc.example.com with scopes profile, email.' not in resp.text
|
||||
|
||||
# same name
|
||||
resp = resp.click('Add new authenticator')
|
||||
resp.form['name'] = 'test'
|
||||
resp.form['authenticator'] = 'oidc'
|
||||
resp.form['ou'] = ou1.pk
|
||||
resp = resp.form.submit().follow()
|
||||
assert OIDCProvider.objects.filter(slug='test-1').count() == 1
|
||||
OIDCProvider.objects.filter(slug='test-1').delete()
|
||||
|
||||
# OU is required
|
||||
resp = app.get('/manage/authenticators/add/')
|
||||
resp.form['name'] = 'test'
|
||||
resp.form['authenticator'] = 'oidc'
|
||||
resp.form['ou'] = ''
|
||||
resp = resp.form.submit()
|
||||
assert 'This field is required' in resp.text
|
||||
|
||||
resp = app.get('/manage/authenticators/')
|
||||
resp = resp.click('Configure', index=1)
|
||||
resp = resp.click('Delete')
|
||||
resp = resp.form.submit().follow()
|
||||
assert not OIDCProvider.objects.filter(slug='test').exists()
|
||||
|
|
Loading…
Reference in New Issue