From a80dd255d5ab22f6abadb142114fd39a50a049a5 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 29 Sep 2022 20:04:33 +0200 Subject: [PATCH 04/10] misc: move auth_saml test in directory (#69720) --- tests/auth_saml/__init__.py | 0 tests/auth_saml/test_misc.py | 76 ++++ tests/test_auth_saml.py | 717 ----------------------------------- 3 files changed, 76 insertions(+), 717 deletions(-) create mode 100644 tests/auth_saml/__init__.py create mode 100644 tests/auth_saml/test_misc.py delete mode 100644 tests/test_auth_saml.py diff --git a/tests/auth_saml/__init__.py b/tests/auth_saml/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/auth_saml/test_misc.py b/tests/auth_saml/test_misc.py new file mode 100644 index 00000000..1594f0c7 --- /dev/null +++ b/tests/auth_saml/test_misc.py @@ -0,0 +1,76 @@ +# 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 . + +from mellon.models import Issuer, UserSAMLIdentifier + +from authentic2.custom_user.models import DeletedUser, User +from authentic2_auth_saml.models import SAMLAttributeLookup, SAMLAuthenticator + + +def test_save_account_on_delete_user(db): + user = User.objects.create() + issuer1, _ = Issuer.objects.get_or_create(entity_id='https://idp1.com/') + UserSAMLIdentifier.objects.create(user=user, issuer=issuer1, name_id='1234') + issuer2, _ = Issuer.objects.get_or_create(entity_id='https://idp2.com/') + UserSAMLIdentifier.objects.create(user=user, issuer=issuer2, name_id='4567') + + user.delete() + assert UserSAMLIdentifier.objects.count() == 0 + + deleted_user = DeletedUser.objects.get() + assert deleted_user.old_data.get('saml_accounts') == [ + { + 'issuer': 'https://idp1.com/', + 'name_id': '1234', + }, + { + 'issuer': 'https://idp2.com/', + 'name_id': '4567', + }, + ] + + +def test_saml_authenticator_settings(db): + authenticator = SAMLAuthenticator.objects.create( + enabled=True, metadata='meta1.xml', slug='idp1', authn_classref='a, b' + ) + + assert 'METADATA' in authenticator.settings + assert 'METADATA_PATH' not in authenticator.settings + assert 'METADATA_URL' not in authenticator.settings + assert authenticator.settings['AUTHN_CLASSREF'] == ['a', 'b'] + + authenticator.metadata = '' + authenticator.metadata_path = '/some/path/metadata.xml' + authenticator.save() + + assert 'METADATA_PATH' in authenticator.settings + assert 'METADATA' not in authenticator.settings + assert 'METADATA_URL' not in authenticator.settings + + authenticator.authn_classref = '' + authenticator.save() + + assert authenticator.settings['AUTHN_CLASSREF'] == [] + + SAMLAttributeLookup.objects.create( + authenticator=authenticator, + user_field='email', + saml_attribute='mail', + ) + assert authenticator.settings['LOOKUP_BY_ATTRIBUTES'] == [ + {'saml_attribute': 'mail', 'user_field': 'email', 'ignore-case': False} + ] diff --git a/tests/test_auth_saml.py b/tests/test_auth_saml.py deleted file mode 100644 index 9215aa0d..00000000 --- a/tests/test_auth_saml.py +++ /dev/null @@ -1,717 +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 . - -import os -import re -from unittest import mock - -import lasso -import pytest -from django.contrib.auth import get_user_model -from mellon.adapters import UserCreationError -from mellon.models import Issuer, UserSAMLIdentifier - -from authentic2.apps.authenticators.models import LoginPasswordAuthenticator -from authentic2.custom_user.models import DeletedUser -from authentic2.models import Attribute -from authentic2_auth_saml.adapters import AuthenticAdapter, MappingError -from authentic2_auth_saml.models import ( - AddRoleAction, - SAMLAttributeLookup, - SAMLAuthenticator, - SetAttributeAction, -) - -from .utils import login - -User = get_user_model() - - -@pytest.fixture -def patched_adapter(monkeypatch): - def load_idp(self, settings, order): - settings['ENTITY_ID'] = 'idp1' - return settings - - monkeypatch.setattr(AuthenticAdapter, 'load_idp', load_idp) - - -def test_providers_on_login_page(db, app, settings): - SAMLAuthenticator.objects.create( - enabled=True, - metadata='meta1.xml', - slug='idp1', - button_label='Test label', - button_description='This is a test.', - ) - - response = app.get('/login/') - assert response.pyquery('button[name="login-saml-idp1"]') - assert not response.pyquery('button[name="login-saml-1"]') - assert 'SAML' in response.text - - SAMLAuthenticator.objects.create(enabled=True, metadata='meta1.xml', slug='idp2') - response = app.get('/login/') - # two frontends should be present on login page - assert response.pyquery('button[name="login-saml-idp1"]') - assert response.pyquery('button[name="login-saml-idp2"]') - assert 'Test label' in response.text - assert 'This is a test.' in response.text - - -@pytest.fixture -def adapter(): - return AuthenticAdapter() - - -@pytest.fixture -def idp(db): - authenticator = SAMLAuthenticator.objects.create( - enabled=True, - metadata='meta1.xml', - slug='idp1', - ) - SetAttributeAction.objects.create( - authenticator=authenticator, - user_field='email', - saml_attribute='mail', - mandatory=True, - ) - SetAttributeAction.objects.create( - authenticator=authenticator, - user_field='title', - saml_attribute='title', - ) - SetAttributeAction.objects.create( - authenticator=authenticator, - user_field='first_name', - saml_attribute='http://nice/attribute/givenName', - ) - return authenticator.settings - - -@pytest.fixture -def title_attribute(db): - return Attribute.objects.create(kind='title', name='title', label='title') - - -@pytest.fixture -def saml_attributes(): - return { - 'issuer': 'https://idp.com/', - 'name_id_content': 'xxx', - 'name_id_format': lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT, - 'mail': ['john.doe@example.com'], - 'title': ['Mr.'], - 'http://nice/attribute/givenName': ['John'], - } - - -@pytest.fixture -def user(db): - return User.objects.create() - - -def test_lookup_user_ok(adapter, idp, saml_attributes, title_attribute): - assert User.objects.count() == 0 - - user = adapter.lookup_user(idp, saml_attributes) - user.refresh_from_db() - assert user.email == 'john.doe@example.com' - assert user.attributes.title == 'Mr.' - assert user.first_name == 'John' - assert user.attributes.title == 'Mr.' - assert user.ou.default is True - - -def test_lookup_user_missing_mandatory_attribute(adapter, idp, saml_attributes, title_attribute): - del saml_attributes['mail'] - - assert User.objects.count() == 0 - assert adapter.lookup_user(idp, saml_attributes) is None - assert User.objects.count() == 0 - - -def test_apply_attribute_mapping_missing_attribute_logged( - caplog, adapter, idp, saml_attributes, title_attribute, user -): - caplog.set_level('WARNING') - saml_attributes['http://nice/attribute/givenName'] = [] - adapter.provision_a2_attributes(user, idp, saml_attributes) - assert re.match('.*no value.*first_name', caplog.records[-1].message) - - -def test_apply_attribute_mapping_missing_attribute_exception( - adapter, idp, saml_attributes, title_attribute, user, rf -): - saml_attributes['http://nice/attribute/givenName'] = [] - SetAttributeAction.objects.filter(user_field='first_name').update(mandatory=True) - with pytest.raises(MappingError, match='no value'): - adapter.provision_a2_attributes(user, idp, saml_attributes) - - request = rf.get('/') - request._messages = mock.Mock() - adapter.request = request - with pytest.raises(UserCreationError): - adapter.finish_create_user(idp, saml_attributes, user) - request._messages.add.assert_called_once_with( - 40, 'User creation failed: no value for attribute "first_name".', '' - ) - - -@pytest.mark.parametrize('action_name', ['add-role', 'toggle-role']) -class TestAddRole: - @pytest.fixture - def idp(self, action_name, simple_role): - authenticator = SAMLAuthenticator.objects.create( - enabled=True, - metadata='meta1.xml', - slug='idp1', - ) - AddRoleAction.objects.create(authenticator=authenticator, role=simple_role) - return authenticator.settings - - @pytest.fixture - def saml_attributes(self): - return { - 'issuer': 'https://idp.com/', - 'name_id_content': 'xxx', - 'name_id_format': lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT, - } - - def test_lookup_user_success(self, adapter, simple_role, idp, saml_attributes): - user = adapter.lookup_user(idp, saml_attributes) - assert simple_role in user.roles.all() - - -def test_login_with_conditionnal_authenticators(db, app, settings, caplog): - authenticator = SAMLAuthenticator.objects.create( - enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp1' - ) - - response = app.get('/login/') - assert 'login-saml-idp1' in response - - authenticator.show_condition = 'remote_addr==\'0.0.0.0\'' - authenticator.save() - response = app.get('/login/') - assert 'login-saml-idp1' not in response - - authenticator2 = SAMLAuthenticator.objects.create( - enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp2' - ) - response = app.get('/login/') - assert 'login-saml-idp1' not in response - assert 'login-saml-idp2' in response - - authenticator2.show_condition = 'remote_addr==\'0.0.0.0\'' - authenticator2.save() - response = app.get('/login/') - assert 'login-saml-idp1' not in response - assert 'login-saml-idp2' not in response - - -def test_login_condition_dnsbl(db, app, settings, caplog): - SAMLAuthenticator.objects.create( - enabled=True, - metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), - slug='idp1', - show_condition='remote_addr in dnsbl(\'dnswl.example.com\')', - ) - SAMLAuthenticator.objects.create( - enabled=True, - metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), - slug='idp2', - show_condition='remote_addr not in dnsbl(\'dnswl.example.com\')', - ) - with mock.patch('authentic2.utils.evaluate.check_dnsbl', return_value=True): - response = app.get('/login/') - assert 'login-saml-idp1' in response - assert 'login-saml-idp2' not in response - - -def test_login_autorun(db, app, settings, patched_adapter): - response = app.get('/login/') - - authenticator = SAMLAuthenticator.objects.create( - enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp1' - ) - # hide password block - LoginPasswordAuthenticator.objects.update_or_create( - slug='password-authenticator', defaults={'enabled': False} - ) - response = app.get('/login/', status=302) - assert '/accounts/saml/login/?entityID=' in response['Location'] - - authenticator.slug = 'slug_with_underscore' - authenticator.save() - response = app.get('/login/', status=302) - assert '/accounts/saml/login/?entityID=' in response['Location'] - - -def test_save_account_on_delete_user(db): - user = User.objects.create() - issuer1, _ = Issuer.objects.get_or_create(entity_id='https://idp1.com/') - UserSAMLIdentifier.objects.create(user=user, issuer=issuer1, name_id='1234') - issuer2, _ = Issuer.objects.get_or_create(entity_id='https://idp2.com/') - UserSAMLIdentifier.objects.create(user=user, issuer=issuer2, name_id='4567') - - user.delete() - assert UserSAMLIdentifier.objects.count() == 0 - - deleted_user = DeletedUser.objects.get() - assert deleted_user.old_data.get('saml_accounts') == [ - { - 'issuer': 'https://idp1.com/', - 'name_id': '1234', - }, - { - 'issuer': 'https://idp2.com/', - 'name_id': '4567', - }, - ] - - -def test_manager_user_sidebar(app, superuser, simple_user): - login(app, superuser, '/manage/') - response = app.get('/manage/users/%s/' % simple_user.id) - assert 'SAML' not in response - - issuer1, _ = Issuer.objects.get_or_create(entity_id='https://idp1.com/') - UserSAMLIdentifier.objects.create(user=simple_user, issuer=issuer1, name_id='1234') - - response = app.get('/manage/users/%s/' % simple_user.id) - assert 'SAML' in response - assert 'https://idp1.com/' in response - assert '1234' in response - - -def test_saml_authenticator_settings(db): - authenticator = SAMLAuthenticator.objects.create( - enabled=True, metadata='meta1.xml', slug='idp1', authn_classref='a, b' - ) - - assert 'METADATA' in authenticator.settings - assert 'METADATA_PATH' not in authenticator.settings - assert 'METADATA_URL' not in authenticator.settings - assert authenticator.settings['AUTHN_CLASSREF'] == ['a', 'b'] - - authenticator.metadata = '' - authenticator.metadata_path = '/some/path/metadata.xml' - authenticator.save() - - assert 'METADATA_PATH' in authenticator.settings - assert 'METADATA' not in authenticator.settings - assert 'METADATA_URL' not in authenticator.settings - - authenticator.authn_classref = '' - authenticator.save() - - assert authenticator.settings['AUTHN_CLASSREF'] == [] - - SAMLAttributeLookup.objects.create( - authenticator=authenticator, - user_field='email', - saml_attribute='mail', - ) - assert authenticator.settings['LOOKUP_BY_ATTRIBUTES'] == [ - {'saml_attribute': 'mail', 'user_field': 'email', 'ignore-case': False} - ] - - -def test_saml_authenticator_data_migration(migration, settings): - app = 'authentic2_auth_saml' - migrate_from = [(app, '0001_initial')] - migrate_to = [(app, '0002_auto_20220608_1559')] - - old_apps = migration.before(migrate_from) - SAMLAuthenticator = old_apps.get_model(app, 'SAMLAuthenticator') - - settings.A2_AUTH_SAML_ENABLE = True - settings.MELLON_METADATA_CACHE_TIME = 42 - settings.MELLON_METADATA_HTTP_TIMEOUT = 42 - settings.MELLON_PROVISION = False - settings.MELLON_VERIFY_SSL_CERTIFICATE = True - settings.MELLON_TRANSIENT_FEDERATION_ATTRIBUTE = None - settings.MELLON_USERNAME_TEMPLATE = 'test' - settings.MELLON_NAME_ID_POLICY_ALLOW_CREATE = False - settings.MELLON_FORCE_AUTHN = True - settings.MELLON_ADD_AUTHNREQUEST_NEXT_URL_EXTENSION = False - settings.MELLON_GROUP_ATTRIBUTE = 'role' - settings.MELLON_CREATE_GROUP = True - settings.MELLON_ERROR_URL = 'https://example.com/error/' - settings.MELLON_AUTHN_CLASSREF = ('class1', 'class2') - settings.MELLON_LOGIN_HINTS = ['hint1', 'hint2'] - settings.AUTH_FRONTENDS_KWARGS = { - 'saml': { - 'priority': 1, - 'show_condition': { - '0': 'first condition', - '1': 'second condition', - }, - } - } - settings.MELLON_IDENTITY_PROVIDERS = [ - { - 'METADATA': os.path.join(os.path.dirname(__file__), 'metadata.xml'), - 'REALM': 'test', - 'METADATA_CACHE_TIME': 43, - 'METADATA_HTTP_TIMEOUT': 43, - 'PROVISION': True, - 'LOOKUP_BY_ATTRIBUTES': [], - }, - { - 'METADATA_PATH': os.path.join(os.path.dirname(__file__), 'metadata.xml'), - 'NAME_ID_POLICY_ALLOW_CREATE': True, - 'FORCE_AUTHN': False, - 'ADD_AUTHNREQUEST_NEXT_URL_EXTENSION': True, - 'A2_ATTRIBUTE_MAPPING': [ - { - 'attribute': 'email', - 'saml_attribute': 'mail', - }, - ], - 'LOOKUP_BY_ATTRIBUTES': [{'saml_attribute': 'email', 'user_field': 'email'}], - }, - { - 'METADATA_URL': 'https://example.com/metadata.xml', - 'SLUG': 'third', - 'ATTRIBUTE_MAPPING': {'email': 'attributes[mail][0]'}, - 'SUPERUSER_MAPPING': {'roles': 'Admin'}, - }, - ] - - new_apps = migration.apply(migrate_to) - SAMLAuthenticator = new_apps.get_model(app, 'SAMLAuthenticator') - first_authenticator, second_authenticator, third_authenticator = SAMLAuthenticator.objects.all() - assert first_authenticator.slug == '0' - assert first_authenticator.order == 1 - assert first_authenticator.show_condition == 'first condition' - assert first_authenticator.enabled is True - assert first_authenticator.metadata_path == os.path.join(os.path.dirname(__file__), 'metadata.xml') - assert first_authenticator.metadata_url == '' - assert first_authenticator.metadata_cache_time == 43 - assert first_authenticator.metadata_http_timeout == 43 - assert first_authenticator.provision is True - assert first_authenticator.verify_ssl_certificate is True - assert first_authenticator.transient_federation_attribute == '' - assert first_authenticator.realm == 'test' - assert first_authenticator.username_template == 'test' - assert first_authenticator.name_id_policy_format == '' - assert first_authenticator.name_id_policy_allow_create is False - assert first_authenticator.force_authn is True - assert first_authenticator.add_authnrequest_next_url_extension is False - assert first_authenticator.group_attribute == 'role' - assert first_authenticator.create_group is True - assert first_authenticator.error_url == 'https://example.com/error/' - assert first_authenticator.error_redirect_after_timeout == 120 - assert first_authenticator.authn_classref == 'class1, class2' - assert first_authenticator.login_hints == 'hint1, hint2' - assert first_authenticator.lookup_by_attributes == [] - assert first_authenticator.a2_attribute_mapping == [] - assert first_authenticator.attribute_mapping == {} - assert first_authenticator.superuser_mapping == {} - - assert second_authenticator.slug == '1' - assert second_authenticator.order == 1 - assert second_authenticator.show_condition == 'second condition' - assert second_authenticator.enabled is True - assert second_authenticator.metadata_path == os.path.join(os.path.dirname(__file__), 'metadata.xml') - assert second_authenticator.metadata_url == '' - assert second_authenticator.metadata_cache_time == 42 - assert second_authenticator.metadata_http_timeout == 42 - assert second_authenticator.provision is False - assert second_authenticator.verify_ssl_certificate is True - assert second_authenticator.transient_federation_attribute == '' - assert second_authenticator.realm == 'saml' - assert second_authenticator.username_template == 'test' - assert second_authenticator.name_id_policy_format == '' - assert second_authenticator.name_id_policy_allow_create is True - assert second_authenticator.force_authn is False - assert second_authenticator.add_authnrequest_next_url_extension is True - assert second_authenticator.group_attribute == 'role' - assert second_authenticator.create_group is True - assert second_authenticator.error_url == 'https://example.com/error/' - assert second_authenticator.error_redirect_after_timeout == 120 - assert second_authenticator.authn_classref == 'class1, class2' - assert second_authenticator.login_hints == 'hint1, hint2' - assert second_authenticator.lookup_by_attributes == [{'saml_attribute': 'email', 'user_field': 'email'}] - assert second_authenticator.a2_attribute_mapping == [ - { - 'attribute': 'email', - 'saml_attribute': 'mail', - }, - ] - assert first_authenticator.attribute_mapping == {} - assert first_authenticator.superuser_mapping == {} - - assert third_authenticator.slug == 'third' - assert third_authenticator.order == 1 - assert third_authenticator.show_condition == '' - assert third_authenticator.enabled is True - assert third_authenticator.metadata_path == '' - assert third_authenticator.metadata_url == 'https://example.com/metadata.xml' - assert third_authenticator.metadata_cache_time == 42 - assert third_authenticator.metadata_http_timeout == 42 - assert third_authenticator.provision is False - assert third_authenticator.verify_ssl_certificate is True - assert third_authenticator.transient_federation_attribute == '' - assert third_authenticator.realm == 'saml' - assert third_authenticator.username_template == 'test' - assert third_authenticator.name_id_policy_format == '' - assert third_authenticator.name_id_policy_format == '' - assert third_authenticator.name_id_policy_allow_create is False - assert third_authenticator.force_authn is True - assert third_authenticator.group_attribute == 'role' - assert third_authenticator.create_group is True - assert third_authenticator.error_url == 'https://example.com/error/' - assert third_authenticator.error_redirect_after_timeout == 120 - assert third_authenticator.authn_classref == 'class1, class2' - assert third_authenticator.login_hints == 'hint1, hint2' - assert third_authenticator.lookup_by_attributes == [ - {'saml_attribute': 'email', 'user_field': 'email', 'ignore-case': True}, - {'saml_attribute': 'username', 'user_field': 'username'}, - ] - assert third_authenticator.a2_attribute_mapping == [] - assert third_authenticator.attribute_mapping == {'email': 'attributes[mail][0]'} - assert third_authenticator.superuser_mapping == {'roles': 'Admin'} - - -def test_saml_authenticator_data_migration_empty_configuration(migration, settings): - app = 'authentic2_auth_saml' - migrate_from = [(app, '0001_initial')] - migrate_to = [(app, '0002_auto_20220608_1559')] - - old_apps = migration.before(migrate_from) - SAMLAuthenticator = old_apps.get_model(app, 'SAMLAuthenticator') - - new_apps = migration.apply(migrate_to) - SAMLAuthenticator = new_apps.get_model(app, 'SAMLAuthenticator') - assert not SAMLAuthenticator.objects.exists() - - -def test_saml_authenticator_data_migration_bad_settings(migration, settings): - app = 'authentic2_auth_saml' - migrate_from = [(app, '0001_initial')] - migrate_to = [(app, '0002_auto_20220608_1559')] - - old_apps = migration.before(migrate_from) - SAMLAuthenticator = old_apps.get_model(app, 'SAMLAuthenticator') - - settings.AUTH_FRONTENDS_KWARGS = {"saml": {"priority": None, "show_condition": None}} - settings.MELLON_METADATA_CACHE_TIME = 2**16 - settings.MELLON_METADATA_HTTP_TIMEOUT = -1 - settings.MELLON_PROVISION = None - settings.MELLON_USERNAME_TEMPLATE = 42 - settings.MELLON_GROUP_ATTRIBUTE = None - settings.MELLON_ERROR_URL = 'a' * 500 - settings.MELLON_AUTHN_CLASSREF = 'not-a-list' - settings.MELLON_IDENTITY_PROVIDERS = [ - { - 'METADATA': os.path.join(os.path.dirname(__file__), 'metadata.xml'), - 'ERROR_REDIRECT_AFTER_TIMEOUT': -1, - 'SUPERUSER_MAPPING': 'not-a-dict', - }, - ] - - new_apps = migration.apply(migrate_to) - SAMLAuthenticator = new_apps.get_model(app, 'SAMLAuthenticator') - authenticator = SAMLAuthenticator.objects.get() - assert authenticator.slug == '0' - assert authenticator.order == 3 - assert authenticator.show_condition == '' - assert authenticator.enabled is False - assert authenticator.metadata_cache_time == 3600 - assert authenticator.metadata_http_timeout == 10 - assert authenticator.provision is True - assert authenticator.username_template == '{attributes[name_id_content]}@{realm}' - assert authenticator.group_attribute == '' - assert authenticator.error_url == 'a' * 200 - assert authenticator.error_redirect_after_timeout == 120 - assert authenticator.authn_classref == '' - assert authenticator.superuser_mapping == {} - - -def test_saml_authenticator_data_migration_json_fields(migration, settings): - migrate_from = [ - ( - 'authentic2_auth_saml', - '0005_addroleaction_renameattributeaction_samlattributelookup_setattributeaction', - ), - ('a2_rbac', '0029_use_unique_constraints'), - ] - migrate_to = [ - ('authentic2_auth_saml', '0006_migrate_jsonfields'), - ('a2_rbac', '0029_use_unique_constraints'), - ] - - old_apps = migration.before(migrate_from) - SAMLAuthenticator = old_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') - Role = old_apps.get_model('a2_rbac', 'Role') - OU = old_apps.get_model('a2_rbac', 'OrganizationalUnit') - - ou = OU.objects.create(name='Test OU', slug='test-ou') - role = Role.objects.create(name='Test role', slug='test-role', ou=ou) - - SAMLAuthenticator.objects.create( - metadata='meta1.xml', - slug='idp1', - lookup_by_attributes=[ - {'saml_attribute': 'email', 'user_field': 'email'}, - {'saml_attribute': 'saml_name', 'user_field': 'first_name', 'ignore-case': True}, - ], - a2_attribute_mapping=[ - { - 'attribute': 'email', - 'saml_attribute': 'mail', - 'mandatory': True, - }, - {'action': 'rename', 'from': 'a' * 1025, 'to': 'first_name'}, - { - 'attribute': 'first_name', - 'saml_attribute': 'first_name', - }, - { - 'attribute': 'invalid', - 'saml_attribute': '', - }, - { - 'attribute': 'invalid', - 'saml_attribute': None, - }, - { - 'attribute': 'invalid', - }, - { - 'action': 'add-role', - 'role': { - 'name': role.name, - 'ou': { - 'name': role.ou.name, - }, - }, - 'condition': "roles == 'A'", - }, - ], - ) - - new_apps = migration.apply(migrate_to) - SAMLAuthenticator = new_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') - authenticator = SAMLAuthenticator.objects.get() - - attribute_lookup1, attribute_lookup2 = authenticator.attribute_lookups.all().order_by('pk') - assert attribute_lookup1.saml_attribute == 'email' - assert attribute_lookup1.user_field == 'email' - assert attribute_lookup1.ignore_case is False - assert attribute_lookup2.saml_attribute == 'saml_name' - assert attribute_lookup2.user_field == 'first_name' - assert attribute_lookup2.ignore_case is True - - set_attribute1, set_attribute2 = authenticator.set_attribute_actions.all().order_by('pk') - assert set_attribute1.attribute == 'email' - assert set_attribute1.saml_attribute == 'mail' - assert set_attribute1.mandatory is True - assert set_attribute2.attribute == 'first_name' - assert set_attribute2.saml_attribute == 'first_name' - assert set_attribute2.mandatory is False - - rename_attribute = authenticator.rename_attribute_actions.get() - assert rename_attribute.from_name == 'a' * 1024 - assert rename_attribute.to_name == 'first_name' - - add_role = authenticator.add_role_actions.get() - assert add_role.role.pk == role.pk - assert add_role.condition == "roles == 'A'" - assert add_role.mandatory is False - - -def test_saml_authenticator_data_migration_json_fields_log_errors(migration, settings, caplog): - migrate_from = [ - ( - 'authentic2_auth_saml', - '0005_addroleaction_renameattributeaction_samlattributelookup_setattributeaction', - ), - ('a2_rbac', '0029_use_unique_constraints'), - ] - migrate_to = [ - ('authentic2_auth_saml', '0006_migrate_jsonfields'), - ('a2_rbac', '0029_use_unique_constraints'), - ] - - old_apps = migration.before(migrate_from) - SAMLAuthenticator = old_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') - - SAMLAuthenticator.objects.create( - metadata='meta1.xml', - slug='idp1', - lookup_by_attributes=[{'saml_attribute': 'email', 'user_field': 'email'}], - a2_attribute_mapping=['bad'], - ) - - new_apps = migration.apply(migrate_to) - SAMLAuthenticator = new_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') - - authenticator = SAMLAuthenticator.objects.get() - assert not authenticator.attribute_lookups.exists() - - assert caplog.messages == [ - 'could not create related objects for authenticator SAMLAuthenticator object (%s)' % authenticator.pk, - 'attribute mapping for SAMLAuthenticator object (%s): ["bad"]' % authenticator.pk, - 'lookup by attributes for SAMLAuthenticator object (%s): [{"user_field": "email", "saml_attribute": "email"}]' - % authenticator.pk, - ] - - -def test_saml_authenticator_data_migration_rename_attributes(migration, settings): - migrate_from = [('authentic2_auth_saml', '0008_auto_20220913_1105')] - migrate_to = [('authentic2_auth_saml', '0009_statically_rename_attributes')] - - old_apps = migration.before(migrate_from) - SAMLAuthenticator = old_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') - RenameAttributeAction = old_apps.get_model('authentic2_auth_saml', 'RenameAttributeAction') - SetAttributeAction = old_apps.get_model('authentic2_auth_saml', 'SetAttributeAction') - SAMLAttributeLookup = old_apps.get_model('authentic2_auth_saml', 'SAMLAttributeLookup') - - authenticator = SAMLAuthenticator.objects.create(slug='idp1') - RenameAttributeAction.objects.create( - authenticator=authenticator, from_name='http://nice/attribute/givenName', to_name='first_name' - ) - SAMLAttributeLookup.objects.create( - authenticator=authenticator, user_field='first_name', saml_attribute='first_name' - ) - SAMLAttributeLookup.objects.create( - authenticator=authenticator, user_field='title', saml_attribute='title' - ) - SetAttributeAction.objects.create( - authenticator=authenticator, user_field='first_name', saml_attribute='first_name' - ) - SetAttributeAction.objects.create(authenticator=authenticator, user_field='title', saml_attribute='title') - - new_apps = migration.apply(migrate_to) - SAMLAuthenticator = new_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') - authenticator = SAMLAuthenticator.objects.get() - - attribute_lookup1, attribute_lookup2 = authenticator.attribute_lookups.all().order_by('pk') - assert attribute_lookup1.saml_attribute == 'http://nice/attribute/givenName' - assert attribute_lookup1.user_field == 'first_name' - assert attribute_lookup2.saml_attribute == 'title' - assert attribute_lookup2.user_field == 'title' - - set_attribute1, set_attribute2 = authenticator.set_attribute_actions.all().order_by('pk') - assert set_attribute1.saml_attribute == 'http://nice/attribute/givenName' - assert set_attribute1.user_field == 'first_name' - assert set_attribute2.saml_attribute == 'title' - assert set_attribute2.user_field == 'title' -- 2.37.2