diff --git a/src/authentic2/views.py b/src/authentic2/views.py index 70fbaed7f..c83b749e4 100644 --- a/src/authentic2/views.py +++ b/src/authentic2/views.py @@ -349,7 +349,11 @@ def passive_login(request, *, next_url, login_hint=None): visible_authenticators = [ authenticator for authenticator in authenticators - if (authenticator.shown(ctx=show_ctx) and getattr(authenticator, 'passive_login', None)) + if ( + authenticator.shown(ctx=show_ctx) + and getattr(authenticator, 'passive_authn_supported', True) + and getattr(authenticator, 'passive_login', None) + ) ] if not visible_authenticators: diff --git a/src/authentic2_auth_oidc/migrations/0014_oidcprovider_passive_authn_supported.py b/src/authentic2_auth_oidc/migrations/0014_oidcprovider_passive_authn_supported.py new file mode 100644 index 000000000..8871a842b --- /dev/null +++ b/src/authentic2_auth_oidc/migrations/0014_oidcprovider_passive_authn_supported.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.26 on 2023-01-17 08:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentic2_auth_oidc', '0013_synchronization_fields'), + ] + + operations = [ + migrations.AddField( + model_name='oidcprovider', + name='passive_authn_supported', + field=models.BooleanField(default=True, verbose_name='Supports passive authentication'), + ), + ] diff --git a/src/authentic2_auth_oidc/models.py b/src/authentic2_auth_oidc/models.py index 1fc531b34..b91c51b33 100644 --- a/src/authentic2_auth_oidc/models.py +++ b/src/authentic2_auth_oidc/models.py @@ -140,6 +140,11 @@ class OIDCProvider(BaseAuthenticator): created = models.DateTimeField(verbose_name=_('creation date'), auto_now_add=True) modified = models.DateTimeField(verbose_name=_('last modification date'), auto_now=True) + # passive authn deactivation flag + passive_authn_supported = models.BooleanField( + verbose_name=_('Supports passive authentication'), + default=True, + ) objects = managers.OIDCProviderManager() type = 'oidc' diff --git a/tests/test_auth_oidc.py b/tests/test_auth_oidc.py index 78c5c5a47..9f1040a9c 100644 --- a/tests/test_auth_oidc.py +++ b/tests/test_auth_oidc.py @@ -43,6 +43,7 @@ from authentic2.apps.authenticators.models import LoginPasswordAuthenticator from authentic2.custom_user.models import DeletedUser from authentic2.models import Attribute, AttributeValue from authentic2.utils.misc import last_authentication_event +from authentic2.views import passive_login from authentic2_auth_oidc.backends import OIDCBackend from authentic2_auth_oidc.models import OIDCAccount, OIDCClaimMapping, OIDCProvider from authentic2_auth_oidc.utils import IDToken, IDTokenError, parse_id_token, register_issuer @@ -1470,3 +1471,53 @@ def test_passive_login(get_provider, rf): _, query = url.split('?', 1) qs = dict(urllib.parse.parse_qsl(query)) assert qs['prompt'] == 'none' + + +@mock.patch('authentic2_auth_oidc.views.get_provider') +def test_passive_login_main_view(get_provider, rf): + AUTHORIZE_URL = 'https://op.example.com/authorize' + SCOPES = {'profile'} + + provider = OIDCProvider.objects.create( + pk=1, + client_id='1234', + authorization_endpoint=AUTHORIZE_URL, + scopes=' '.join(SCOPES), + passive_authn_supported=True, + enabled=True, + ) + get_provider.return_value = provider + req = rf.get('/') + req.user = mock.Mock() + req.user.is_authenticated = False + req.session = {} + + response = passive_login(req, next_url='/manage/') + assert response.status_code == 302 + assert response.url.startswith('https://op.example.com/authorize?') + _, query = response.url.split('?', 1) + qs = dict(urllib.parse.parse_qsl(query)) + assert qs['prompt'] == 'none' + + +@mock.patch('authentic2_auth_oidc.views.get_provider') +def test_passive_login_main_view_deactivated(get_provider, rf): + AUTHORIZE_URL = 'https://op.example.com/authorize' + SCOPES = {'profile'} + + provider = OIDCProvider.objects.create( + pk=1, + client_id='1234', + authorization_endpoint=AUTHORIZE_URL, + scopes=' '.join(SCOPES), + passive_authn_supported=False, + enabled=True, + ) + get_provider.return_value = provider + req = rf.get('/') + req.user = mock.Mock() + req.user.is_authenticated = False + req.session = {} + + response = passive_login(req, next_url='/manage/') + assert response is None