Projet

Général

Profil

0004-misc-move-auth_saml-test-in-directory-69720.patch

Benjamin Dauvergne, 18 octobre 2022 20:36

Télécharger (32,2 ko)

Voir les différences:

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
tests/auth_saml/test_misc.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 mellon.models import Issuer, UserSAMLIdentifier
18

  
19
from authentic2.custom_user.models import DeletedUser, User
20
from authentic2_auth_saml.models import SAMLAttributeLookup, SAMLAuthenticator
21

  
22

  
23
def test_save_account_on_delete_user(db):
24
    user = User.objects.create()
25
    issuer1, _ = Issuer.objects.get_or_create(entity_id='https://idp1.com/')
26
    UserSAMLIdentifier.objects.create(user=user, issuer=issuer1, name_id='1234')
27
    issuer2, _ = Issuer.objects.get_or_create(entity_id='https://idp2.com/')
28
    UserSAMLIdentifier.objects.create(user=user, issuer=issuer2, name_id='4567')
29

  
30
    user.delete()
31
    assert UserSAMLIdentifier.objects.count() == 0
32

  
33
    deleted_user = DeletedUser.objects.get()
34
    assert deleted_user.old_data.get('saml_accounts') == [
35
        {
36
            'issuer': 'https://idp1.com/',
37
            'name_id': '1234',
38
        },
39
        {
40
            'issuer': 'https://idp2.com/',
41
            'name_id': '4567',
42
        },
43
    ]
44

  
45

  
46
def test_saml_authenticator_settings(db):
47
    authenticator = SAMLAuthenticator.objects.create(
48
        enabled=True, metadata='meta1.xml', slug='idp1', authn_classref='a, b'
49
    )
50

  
51
    assert 'METADATA' in authenticator.settings
52
    assert 'METADATA_PATH' not in authenticator.settings
53
    assert 'METADATA_URL' not in authenticator.settings
54
    assert authenticator.settings['AUTHN_CLASSREF'] == ['a', 'b']
55

  
56
    authenticator.metadata = ''
57
    authenticator.metadata_path = '/some/path/metadata.xml'
58
    authenticator.save()
59

  
60
    assert 'METADATA_PATH' in authenticator.settings
61
    assert 'METADATA' not in authenticator.settings
62
    assert 'METADATA_URL' not in authenticator.settings
63

  
64
    authenticator.authn_classref = ''
65
    authenticator.save()
66

  
67
    assert authenticator.settings['AUTHN_CLASSREF'] == []
68

  
69
    SAMLAttributeLookup.objects.create(
70
        authenticator=authenticator,
71
        user_field='email',
72
        saml_attribute='mail',
73
    )
74
    assert authenticator.settings['LOOKUP_BY_ATTRIBUTES'] == [
75
        {'saml_attribute': 'mail', 'user_field': 'email', 'ignore-case': False}
76
    ]
tests/test_auth_saml.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
import os
18
import re
19
from unittest import mock
20

  
21
import lasso
22
import pytest
23
from django.contrib.auth import get_user_model
24
from mellon.adapters import UserCreationError
25
from mellon.models import Issuer, UserSAMLIdentifier
26

  
27
from authentic2.apps.authenticators.models import LoginPasswordAuthenticator
28
from authentic2.custom_user.models import DeletedUser
29
from authentic2.models import Attribute
30
from authentic2_auth_saml.adapters import AuthenticAdapter, MappingError
31
from authentic2_auth_saml.models import (
32
    AddRoleAction,
33
    SAMLAttributeLookup,
34
    SAMLAuthenticator,
35
    SetAttributeAction,
36
)
37

  
38
from .utils import login
39

  
40
User = get_user_model()
41

  
42

  
43
@pytest.fixture
44
def patched_adapter(monkeypatch):
45
    def load_idp(self, settings, order):
46
        settings['ENTITY_ID'] = 'idp1'
47
        return settings
48

  
49
    monkeypatch.setattr(AuthenticAdapter, 'load_idp', load_idp)
50

  
51

  
52
def test_providers_on_login_page(db, app, settings):
53
    SAMLAuthenticator.objects.create(
54
        enabled=True,
55
        metadata='meta1.xml',
56
        slug='idp1',
57
        button_label='Test label',
58
        button_description='This is a test.',
59
    )
60

  
61
    response = app.get('/login/')
62
    assert response.pyquery('button[name="login-saml-idp1"]')
63
    assert not response.pyquery('button[name="login-saml-1"]')
64
    assert 'SAML' in response.text
65

  
66
    SAMLAuthenticator.objects.create(enabled=True, metadata='meta1.xml', slug='idp2')
67
    response = app.get('/login/')
68
    # two frontends should be present on login page
69
    assert response.pyquery('button[name="login-saml-idp1"]')
70
    assert response.pyquery('button[name="login-saml-idp2"]')
71
    assert 'Test label' in response.text
72
    assert 'This is a test.' in response.text
73

  
74

  
75
@pytest.fixture
76
def adapter():
77
    return AuthenticAdapter()
78

  
79

  
80
@pytest.fixture
81
def idp(db):
82
    authenticator = SAMLAuthenticator.objects.create(
83
        enabled=True,
84
        metadata='meta1.xml',
85
        slug='idp1',
86
    )
87
    SetAttributeAction.objects.create(
88
        authenticator=authenticator,
89
        user_field='email',
90
        saml_attribute='mail',
91
        mandatory=True,
92
    )
93
    SetAttributeAction.objects.create(
94
        authenticator=authenticator,
95
        user_field='title',
96
        saml_attribute='title',
97
    )
98
    SetAttributeAction.objects.create(
99
        authenticator=authenticator,
100
        user_field='first_name',
101
        saml_attribute='http://nice/attribute/givenName',
102
    )
103
    return authenticator.settings
104

  
105

  
106
@pytest.fixture
107
def title_attribute(db):
108
    return Attribute.objects.create(kind='title', name='title', label='title')
109

  
110

  
111
@pytest.fixture
112
def saml_attributes():
113
    return {
114
        'issuer': 'https://idp.com/',
115
        'name_id_content': 'xxx',
116
        'name_id_format': lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT,
117
        'mail': ['john.doe@example.com'],
118
        'title': ['Mr.'],
119
        'http://nice/attribute/givenName': ['John'],
120
    }
121

  
122

  
123
@pytest.fixture
124
def user(db):
125
    return User.objects.create()
126

  
127

  
128
def test_lookup_user_ok(adapter, idp, saml_attributes, title_attribute):
129
    assert User.objects.count() == 0
130

  
131
    user = adapter.lookup_user(idp, saml_attributes)
132
    user.refresh_from_db()
133
    assert user.email == 'john.doe@example.com'
134
    assert user.attributes.title == 'Mr.'
135
    assert user.first_name == 'John'
136
    assert user.attributes.title == 'Mr.'
137
    assert user.ou.default is True
138

  
139

  
140
def test_lookup_user_missing_mandatory_attribute(adapter, idp, saml_attributes, title_attribute):
141
    del saml_attributes['mail']
142

  
143
    assert User.objects.count() == 0
144
    assert adapter.lookup_user(idp, saml_attributes) is None
145
    assert User.objects.count() == 0
146

  
147

  
148
def test_apply_attribute_mapping_missing_attribute_logged(
149
    caplog, adapter, idp, saml_attributes, title_attribute, user
150
):
151
    caplog.set_level('WARNING')
152
    saml_attributes['http://nice/attribute/givenName'] = []
153
    adapter.provision_a2_attributes(user, idp, saml_attributes)
154
    assert re.match('.*no value.*first_name', caplog.records[-1].message)
155

  
156

  
157
def test_apply_attribute_mapping_missing_attribute_exception(
158
    adapter, idp, saml_attributes, title_attribute, user, rf
159
):
160
    saml_attributes['http://nice/attribute/givenName'] = []
161
    SetAttributeAction.objects.filter(user_field='first_name').update(mandatory=True)
162
    with pytest.raises(MappingError, match='no value'):
163
        adapter.provision_a2_attributes(user, idp, saml_attributes)
164

  
165
    request = rf.get('/')
166
    request._messages = mock.Mock()
167
    adapter.request = request
168
    with pytest.raises(UserCreationError):
169
        adapter.finish_create_user(idp, saml_attributes, user)
170
    request._messages.add.assert_called_once_with(
171
        40, 'User creation failed: no value for attribute "first_name".', ''
172
    )
173

  
174

  
175
@pytest.mark.parametrize('action_name', ['add-role', 'toggle-role'])
176
class TestAddRole:
177
    @pytest.fixture
178
    def idp(self, action_name, simple_role):
179
        authenticator = SAMLAuthenticator.objects.create(
180
            enabled=True,
181
            metadata='meta1.xml',
182
            slug='idp1',
183
        )
184
        AddRoleAction.objects.create(authenticator=authenticator, role=simple_role)
185
        return authenticator.settings
186

  
187
    @pytest.fixture
188
    def saml_attributes(self):
189
        return {
190
            'issuer': 'https://idp.com/',
191
            'name_id_content': 'xxx',
192
            'name_id_format': lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT,
193
        }
194

  
195
    def test_lookup_user_success(self, adapter, simple_role, idp, saml_attributes):
196
        user = adapter.lookup_user(idp, saml_attributes)
197
        assert simple_role in user.roles.all()
198

  
199

  
200
def test_login_with_conditionnal_authenticators(db, app, settings, caplog):
201
    authenticator = SAMLAuthenticator.objects.create(
202
        enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp1'
203
    )
204

  
205
    response = app.get('/login/')
206
    assert 'login-saml-idp1' in response
207

  
208
    authenticator.show_condition = 'remote_addr==\'0.0.0.0\''
209
    authenticator.save()
210
    response = app.get('/login/')
211
    assert 'login-saml-idp1' not in response
212

  
213
    authenticator2 = SAMLAuthenticator.objects.create(
214
        enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp2'
215
    )
216
    response = app.get('/login/')
217
    assert 'login-saml-idp1' not in response
218
    assert 'login-saml-idp2' in response
219

  
220
    authenticator2.show_condition = 'remote_addr==\'0.0.0.0\''
221
    authenticator2.save()
222
    response = app.get('/login/')
223
    assert 'login-saml-idp1' not in response
224
    assert 'login-saml-idp2' not in response
225

  
226

  
227
def test_login_condition_dnsbl(db, app, settings, caplog):
228
    SAMLAuthenticator.objects.create(
229
        enabled=True,
230
        metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'),
231
        slug='idp1',
232
        show_condition='remote_addr in dnsbl(\'dnswl.example.com\')',
233
    )
234
    SAMLAuthenticator.objects.create(
235
        enabled=True,
236
        metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'),
237
        slug='idp2',
238
        show_condition='remote_addr not in dnsbl(\'dnswl.example.com\')',
239
    )
240
    with mock.patch('authentic2.utils.evaluate.check_dnsbl', return_value=True):
241
        response = app.get('/login/')
242
    assert 'login-saml-idp1' in response
243
    assert 'login-saml-idp2' not in response
244

  
245

  
246
def test_login_autorun(db, app, settings, patched_adapter):
247
    response = app.get('/login/')
248

  
249
    authenticator = SAMLAuthenticator.objects.create(
250
        enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp1'
251
    )
252
    # hide password block
253
    LoginPasswordAuthenticator.objects.update_or_create(
254
        slug='password-authenticator', defaults={'enabled': False}
255
    )
256
    response = app.get('/login/', status=302)
257
    assert '/accounts/saml/login/?entityID=' in response['Location']
258

  
259
    authenticator.slug = 'slug_with_underscore'
260
    authenticator.save()
261
    response = app.get('/login/', status=302)
262
    assert '/accounts/saml/login/?entityID=' in response['Location']
263

  
264

  
265
def test_save_account_on_delete_user(db):
266
    user = User.objects.create()
267
    issuer1, _ = Issuer.objects.get_or_create(entity_id='https://idp1.com/')
268
    UserSAMLIdentifier.objects.create(user=user, issuer=issuer1, name_id='1234')
269
    issuer2, _ = Issuer.objects.get_or_create(entity_id='https://idp2.com/')
270
    UserSAMLIdentifier.objects.create(user=user, issuer=issuer2, name_id='4567')
271

  
272
    user.delete()
273
    assert UserSAMLIdentifier.objects.count() == 0
274

  
275
    deleted_user = DeletedUser.objects.get()
276
    assert deleted_user.old_data.get('saml_accounts') == [
277
        {
278
            'issuer': 'https://idp1.com/',
279
            'name_id': '1234',
280
        },
281
        {
282
            'issuer': 'https://idp2.com/',
283
            'name_id': '4567',
284
        },
285
    ]
286

  
287

  
288
def test_manager_user_sidebar(app, superuser, simple_user):
289
    login(app, superuser, '/manage/')
290
    response = app.get('/manage/users/%s/' % simple_user.id)
291
    assert 'SAML' not in response
292

  
293
    issuer1, _ = Issuer.objects.get_or_create(entity_id='https://idp1.com/')
294
    UserSAMLIdentifier.objects.create(user=simple_user, issuer=issuer1, name_id='1234')
295

  
296
    response = app.get('/manage/users/%s/' % simple_user.id)
297
    assert 'SAML' in response
298
    assert 'https://idp1.com/' in response
299
    assert '1234' in response
300

  
301

  
302
def test_saml_authenticator_settings(db):
303
    authenticator = SAMLAuthenticator.objects.create(
304
        enabled=True, metadata='meta1.xml', slug='idp1', authn_classref='a, b'
305
    )
306

  
307
    assert 'METADATA' in authenticator.settings
308
    assert 'METADATA_PATH' not in authenticator.settings
309
    assert 'METADATA_URL' not in authenticator.settings
310
    assert authenticator.settings['AUTHN_CLASSREF'] == ['a', 'b']
311

  
312
    authenticator.metadata = ''
313
    authenticator.metadata_path = '/some/path/metadata.xml'
314
    authenticator.save()
315

  
316
    assert 'METADATA_PATH' in authenticator.settings
317
    assert 'METADATA' not in authenticator.settings
318
    assert 'METADATA_URL' not in authenticator.settings
319

  
320
    authenticator.authn_classref = ''
321
    authenticator.save()
322

  
323
    assert authenticator.settings['AUTHN_CLASSREF'] == []
324

  
325
    SAMLAttributeLookup.objects.create(
326
        authenticator=authenticator,
327
        user_field='email',
328
        saml_attribute='mail',
329
    )
330
    assert authenticator.settings['LOOKUP_BY_ATTRIBUTES'] == [
331
        {'saml_attribute': 'mail', 'user_field': 'email', 'ignore-case': False}
332
    ]
333

  
334

  
335
def test_saml_authenticator_data_migration(migration, settings):
336
    app = 'authentic2_auth_saml'
337
    migrate_from = [(app, '0001_initial')]
338
    migrate_to = [(app, '0002_auto_20220608_1559')]
339

  
340
    old_apps = migration.before(migrate_from)
341
    SAMLAuthenticator = old_apps.get_model(app, 'SAMLAuthenticator')
342

  
343
    settings.A2_AUTH_SAML_ENABLE = True
344
    settings.MELLON_METADATA_CACHE_TIME = 42
345
    settings.MELLON_METADATA_HTTP_TIMEOUT = 42
346
    settings.MELLON_PROVISION = False
347
    settings.MELLON_VERIFY_SSL_CERTIFICATE = True
348
    settings.MELLON_TRANSIENT_FEDERATION_ATTRIBUTE = None
349
    settings.MELLON_USERNAME_TEMPLATE = 'test'
350
    settings.MELLON_NAME_ID_POLICY_ALLOW_CREATE = False
351
    settings.MELLON_FORCE_AUTHN = True
352
    settings.MELLON_ADD_AUTHNREQUEST_NEXT_URL_EXTENSION = False
353
    settings.MELLON_GROUP_ATTRIBUTE = 'role'
354
    settings.MELLON_CREATE_GROUP = True
355
    settings.MELLON_ERROR_URL = 'https://example.com/error/'
356
    settings.MELLON_AUTHN_CLASSREF = ('class1', 'class2')
357
    settings.MELLON_LOGIN_HINTS = ['hint1', 'hint2']
358
    settings.AUTH_FRONTENDS_KWARGS = {
359
        'saml': {
360
            'priority': 1,
361
            'show_condition': {
362
                '0': 'first condition',
363
                '1': 'second condition',
364
            },
365
        }
366
    }
367
    settings.MELLON_IDENTITY_PROVIDERS = [
368
        {
369
            'METADATA': os.path.join(os.path.dirname(__file__), 'metadata.xml'),
370
            'REALM': 'test',
371
            'METADATA_CACHE_TIME': 43,
372
            'METADATA_HTTP_TIMEOUT': 43,
373
            'PROVISION': True,
374
            'LOOKUP_BY_ATTRIBUTES': [],
375
        },
376
        {
377
            'METADATA_PATH': os.path.join(os.path.dirname(__file__), 'metadata.xml'),
378
            'NAME_ID_POLICY_ALLOW_CREATE': True,
379
            'FORCE_AUTHN': False,
380
            'ADD_AUTHNREQUEST_NEXT_URL_EXTENSION': True,
381
            'A2_ATTRIBUTE_MAPPING': [
382
                {
383
                    'attribute': 'email',
384
                    'saml_attribute': 'mail',
385
                },
386
            ],
387
            'LOOKUP_BY_ATTRIBUTES': [{'saml_attribute': 'email', 'user_field': 'email'}],
388
        },
389
        {
390
            'METADATA_URL': 'https://example.com/metadata.xml',
391
            'SLUG': 'third',
392
            'ATTRIBUTE_MAPPING': {'email': 'attributes[mail][0]'},
393
            'SUPERUSER_MAPPING': {'roles': 'Admin'},
394
        },
395
    ]
396

  
397
    new_apps = migration.apply(migrate_to)
398
    SAMLAuthenticator = new_apps.get_model(app, 'SAMLAuthenticator')
399
    first_authenticator, second_authenticator, third_authenticator = SAMLAuthenticator.objects.all()
400
    assert first_authenticator.slug == '0'
401
    assert first_authenticator.order == 1
402
    assert first_authenticator.show_condition == 'first condition'
403
    assert first_authenticator.enabled is True
404
    assert first_authenticator.metadata_path == os.path.join(os.path.dirname(__file__), 'metadata.xml')
405
    assert first_authenticator.metadata_url == ''
406
    assert first_authenticator.metadata_cache_time == 43
407
    assert first_authenticator.metadata_http_timeout == 43
408
    assert first_authenticator.provision is True
409
    assert first_authenticator.verify_ssl_certificate is True
410
    assert first_authenticator.transient_federation_attribute == ''
411
    assert first_authenticator.realm == 'test'
412
    assert first_authenticator.username_template == 'test'
413
    assert first_authenticator.name_id_policy_format == ''
414
    assert first_authenticator.name_id_policy_allow_create is False
415
    assert first_authenticator.force_authn is True
416
    assert first_authenticator.add_authnrequest_next_url_extension is False
417
    assert first_authenticator.group_attribute == 'role'
418
    assert first_authenticator.create_group is True
419
    assert first_authenticator.error_url == 'https://example.com/error/'
420
    assert first_authenticator.error_redirect_after_timeout == 120
421
    assert first_authenticator.authn_classref == 'class1, class2'
422
    assert first_authenticator.login_hints == 'hint1, hint2'
423
    assert first_authenticator.lookup_by_attributes == []
424
    assert first_authenticator.a2_attribute_mapping == []
425
    assert first_authenticator.attribute_mapping == {}
426
    assert first_authenticator.superuser_mapping == {}
427

  
428
    assert second_authenticator.slug == '1'
429
    assert second_authenticator.order == 1
430
    assert second_authenticator.show_condition == 'second condition'
431
    assert second_authenticator.enabled is True
432
    assert second_authenticator.metadata_path == os.path.join(os.path.dirname(__file__), 'metadata.xml')
433
    assert second_authenticator.metadata_url == ''
434
    assert second_authenticator.metadata_cache_time == 42
435
    assert second_authenticator.metadata_http_timeout == 42
436
    assert second_authenticator.provision is False
437
    assert second_authenticator.verify_ssl_certificate is True
438
    assert second_authenticator.transient_federation_attribute == ''
439
    assert second_authenticator.realm == 'saml'
440
    assert second_authenticator.username_template == 'test'
441
    assert second_authenticator.name_id_policy_format == ''
442
    assert second_authenticator.name_id_policy_allow_create is True
443
    assert second_authenticator.force_authn is False
444
    assert second_authenticator.add_authnrequest_next_url_extension is True
445
    assert second_authenticator.group_attribute == 'role'
446
    assert second_authenticator.create_group is True
447
    assert second_authenticator.error_url == 'https://example.com/error/'
448
    assert second_authenticator.error_redirect_after_timeout == 120
449
    assert second_authenticator.authn_classref == 'class1, class2'
450
    assert second_authenticator.login_hints == 'hint1, hint2'
451
    assert second_authenticator.lookup_by_attributes == [{'saml_attribute': 'email', 'user_field': 'email'}]
452
    assert second_authenticator.a2_attribute_mapping == [
453
        {
454
            'attribute': 'email',
455
            'saml_attribute': 'mail',
456
        },
457
    ]
458
    assert first_authenticator.attribute_mapping == {}
459
    assert first_authenticator.superuser_mapping == {}
460

  
461
    assert third_authenticator.slug == 'third'
462
    assert third_authenticator.order == 1
463
    assert third_authenticator.show_condition == ''
464
    assert third_authenticator.enabled is True
465
    assert third_authenticator.metadata_path == ''
466
    assert third_authenticator.metadata_url == 'https://example.com/metadata.xml'
467
    assert third_authenticator.metadata_cache_time == 42
468
    assert third_authenticator.metadata_http_timeout == 42
469
    assert third_authenticator.provision is False
470
    assert third_authenticator.verify_ssl_certificate is True
471
    assert third_authenticator.transient_federation_attribute == ''
472
    assert third_authenticator.realm == 'saml'
473
    assert third_authenticator.username_template == 'test'
474
    assert third_authenticator.name_id_policy_format == ''
475
    assert third_authenticator.name_id_policy_format == ''
476
    assert third_authenticator.name_id_policy_allow_create is False
477
    assert third_authenticator.force_authn is True
478
    assert third_authenticator.group_attribute == 'role'
479
    assert third_authenticator.create_group is True
480
    assert third_authenticator.error_url == 'https://example.com/error/'
481
    assert third_authenticator.error_redirect_after_timeout == 120
482
    assert third_authenticator.authn_classref == 'class1, class2'
483
    assert third_authenticator.login_hints == 'hint1, hint2'
484
    assert third_authenticator.lookup_by_attributes == [
485
        {'saml_attribute': 'email', 'user_field': 'email', 'ignore-case': True},
486
        {'saml_attribute': 'username', 'user_field': 'username'},
487
    ]
488
    assert third_authenticator.a2_attribute_mapping == []
489
    assert third_authenticator.attribute_mapping == {'email': 'attributes[mail][0]'}
490
    assert third_authenticator.superuser_mapping == {'roles': 'Admin'}
491

  
492

  
493
def test_saml_authenticator_data_migration_empty_configuration(migration, settings):
494
    app = 'authentic2_auth_saml'
495
    migrate_from = [(app, '0001_initial')]
496
    migrate_to = [(app, '0002_auto_20220608_1559')]
497

  
498
    old_apps = migration.before(migrate_from)
499
    SAMLAuthenticator = old_apps.get_model(app, 'SAMLAuthenticator')
500

  
501
    new_apps = migration.apply(migrate_to)
502
    SAMLAuthenticator = new_apps.get_model(app, 'SAMLAuthenticator')
503
    assert not SAMLAuthenticator.objects.exists()
504

  
505

  
506
def test_saml_authenticator_data_migration_bad_settings(migration, settings):
507
    app = 'authentic2_auth_saml'
508
    migrate_from = [(app, '0001_initial')]
509
    migrate_to = [(app, '0002_auto_20220608_1559')]
510

  
511
    old_apps = migration.before(migrate_from)
512
    SAMLAuthenticator = old_apps.get_model(app, 'SAMLAuthenticator')
513

  
514
    settings.AUTH_FRONTENDS_KWARGS = {"saml": {"priority": None, "show_condition": None}}
515
    settings.MELLON_METADATA_CACHE_TIME = 2**16
516
    settings.MELLON_METADATA_HTTP_TIMEOUT = -1
517
    settings.MELLON_PROVISION = None
518
    settings.MELLON_USERNAME_TEMPLATE = 42
519
    settings.MELLON_GROUP_ATTRIBUTE = None
520
    settings.MELLON_ERROR_URL = 'a' * 500
521
    settings.MELLON_AUTHN_CLASSREF = 'not-a-list'
522
    settings.MELLON_IDENTITY_PROVIDERS = [
523
        {
524
            'METADATA': os.path.join(os.path.dirname(__file__), 'metadata.xml'),
525
            'ERROR_REDIRECT_AFTER_TIMEOUT': -1,
526
            'SUPERUSER_MAPPING': 'not-a-dict',
527
        },
528
    ]
529

  
530
    new_apps = migration.apply(migrate_to)
531
    SAMLAuthenticator = new_apps.get_model(app, 'SAMLAuthenticator')
532
    authenticator = SAMLAuthenticator.objects.get()
533
    assert authenticator.slug == '0'
534
    assert authenticator.order == 3
535
    assert authenticator.show_condition == ''
536
    assert authenticator.enabled is False
537
    assert authenticator.metadata_cache_time == 3600
538
    assert authenticator.metadata_http_timeout == 10
539
    assert authenticator.provision is True
540
    assert authenticator.username_template == '{attributes[name_id_content]}@{realm}'
541
    assert authenticator.group_attribute == ''
542
    assert authenticator.error_url == 'a' * 200
543
    assert authenticator.error_redirect_after_timeout == 120
544
    assert authenticator.authn_classref == ''
545
    assert authenticator.superuser_mapping == {}
546

  
547

  
548
def test_saml_authenticator_data_migration_json_fields(migration, settings):
549
    migrate_from = [
550
        (
551
            'authentic2_auth_saml',
552
            '0005_addroleaction_renameattributeaction_samlattributelookup_setattributeaction',
553
        ),
554
        ('a2_rbac', '0029_use_unique_constraints'),
555
    ]
556
    migrate_to = [
557
        ('authentic2_auth_saml', '0006_migrate_jsonfields'),
558
        ('a2_rbac', '0029_use_unique_constraints'),
559
    ]
560

  
561
    old_apps = migration.before(migrate_from)
562
    SAMLAuthenticator = old_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator')
563
    Role = old_apps.get_model('a2_rbac', 'Role')
564
    OU = old_apps.get_model('a2_rbac', 'OrganizationalUnit')
565

  
566
    ou = OU.objects.create(name='Test OU', slug='test-ou')
567
    role = Role.objects.create(name='Test role', slug='test-role', ou=ou)
568

  
569
    SAMLAuthenticator.objects.create(
570
        metadata='meta1.xml',
571
        slug='idp1',
572
        lookup_by_attributes=[
573
            {'saml_attribute': 'email', 'user_field': 'email'},
574
            {'saml_attribute': 'saml_name', 'user_field': 'first_name', 'ignore-case': True},
575
        ],
576
        a2_attribute_mapping=[
577
            {
578
                'attribute': 'email',
579
                'saml_attribute': 'mail',
580
                'mandatory': True,
581
            },
582
            {'action': 'rename', 'from': 'a' * 1025, 'to': 'first_name'},
583
            {
584
                'attribute': 'first_name',
585
                'saml_attribute': 'first_name',
586
            },
587
            {
588
                'attribute': 'invalid',
589
                'saml_attribute': '',
590
            },
591
            {
592
                'attribute': 'invalid',
593
                'saml_attribute': None,
594
            },
595
            {
596
                'attribute': 'invalid',
597
            },
598
            {
599
                'action': 'add-role',
600
                'role': {
601
                    'name': role.name,
602
                    'ou': {
603
                        'name': role.ou.name,
604
                    },
605
                },
606
                'condition': "roles == 'A'",
607
            },
608
        ],
609
    )
610

  
611
    new_apps = migration.apply(migrate_to)
612
    SAMLAuthenticator = new_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator')
613
    authenticator = SAMLAuthenticator.objects.get()
614

  
615
    attribute_lookup1, attribute_lookup2 = authenticator.attribute_lookups.all().order_by('pk')
616
    assert attribute_lookup1.saml_attribute == 'email'
617
    assert attribute_lookup1.user_field == 'email'
618
    assert attribute_lookup1.ignore_case is False
619
    assert attribute_lookup2.saml_attribute == 'saml_name'
620
    assert attribute_lookup2.user_field == 'first_name'
621
    assert attribute_lookup2.ignore_case is True
622

  
623
    set_attribute1, set_attribute2 = authenticator.set_attribute_actions.all().order_by('pk')
624
    assert set_attribute1.attribute == 'email'
625
    assert set_attribute1.saml_attribute == 'mail'
626
    assert set_attribute1.mandatory is True
627
    assert set_attribute2.attribute == 'first_name'
628
    assert set_attribute2.saml_attribute == 'first_name'
629
    assert set_attribute2.mandatory is False
630

  
631
    rename_attribute = authenticator.rename_attribute_actions.get()
632
    assert rename_attribute.from_name == 'a' * 1024
633
    assert rename_attribute.to_name == 'first_name'
634

  
635
    add_role = authenticator.add_role_actions.get()
636
    assert add_role.role.pk == role.pk
637
    assert add_role.condition == "roles == 'A'"
638
    assert add_role.mandatory is False
639

  
640

  
641
def test_saml_authenticator_data_migration_json_fields_log_errors(migration, settings, caplog):
642
    migrate_from = [
643
        (
644
            'authentic2_auth_saml',
645
            '0005_addroleaction_renameattributeaction_samlattributelookup_setattributeaction',
646
        ),
647
        ('a2_rbac', '0029_use_unique_constraints'),
648
    ]
649
    migrate_to = [
650
        ('authentic2_auth_saml', '0006_migrate_jsonfields'),
651
        ('a2_rbac', '0029_use_unique_constraints'),
652
    ]
653

  
654
    old_apps = migration.before(migrate_from)
655
    SAMLAuthenticator = old_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator')
656

  
657
    SAMLAuthenticator.objects.create(
658
        metadata='meta1.xml',
659
        slug='idp1',
660
        lookup_by_attributes=[{'saml_attribute': 'email', 'user_field': 'email'}],
661
        a2_attribute_mapping=['bad'],
662
    )
663

  
664
    new_apps = migration.apply(migrate_to)
665
    SAMLAuthenticator = new_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator')
666

  
667
    authenticator = SAMLAuthenticator.objects.get()
668
    assert not authenticator.attribute_lookups.exists()
669

  
670
    assert caplog.messages == [
671
        'could not create related objects for authenticator SAMLAuthenticator object (%s)' % authenticator.pk,
672
        'attribute mapping for SAMLAuthenticator object (%s): ["bad"]' % authenticator.pk,
673
        'lookup by attributes for SAMLAuthenticator object (%s): [{"user_field": "email", "saml_attribute": "email"}]'
674
        % authenticator.pk,
675
    ]
676

  
677

  
678
def test_saml_authenticator_data_migration_rename_attributes(migration, settings):
679
    migrate_from = [('authentic2_auth_saml', '0008_auto_20220913_1105')]
680
    migrate_to = [('authentic2_auth_saml', '0009_statically_rename_attributes')]
681

  
682
    old_apps = migration.before(migrate_from)
683
    SAMLAuthenticator = old_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator')
684
    RenameAttributeAction = old_apps.get_model('authentic2_auth_saml', 'RenameAttributeAction')
685
    SetAttributeAction = old_apps.get_model('authentic2_auth_saml', 'SetAttributeAction')
686
    SAMLAttributeLookup = old_apps.get_model('authentic2_auth_saml', 'SAMLAttributeLookup')
687

  
688
    authenticator = SAMLAuthenticator.objects.create(slug='idp1')
689
    RenameAttributeAction.objects.create(
690
        authenticator=authenticator, from_name='http://nice/attribute/givenName', to_name='first_name'
691
    )
692
    SAMLAttributeLookup.objects.create(
693
        authenticator=authenticator, user_field='first_name', saml_attribute='first_name'
694
    )
695
    SAMLAttributeLookup.objects.create(
696
        authenticator=authenticator, user_field='title', saml_attribute='title'
697
    )
698
    SetAttributeAction.objects.create(
699
        authenticator=authenticator, user_field='first_name', saml_attribute='first_name'
700
    )
701
    SetAttributeAction.objects.create(authenticator=authenticator, user_field='title', saml_attribute='title')
702

  
703
    new_apps = migration.apply(migrate_to)
704
    SAMLAuthenticator = new_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator')
705
    authenticator = SAMLAuthenticator.objects.get()
706

  
707
    attribute_lookup1, attribute_lookup2 = authenticator.attribute_lookups.all().order_by('pk')
708
    assert attribute_lookup1.saml_attribute == 'http://nice/attribute/givenName'
709
    assert attribute_lookup1.user_field == 'first_name'
710
    assert attribute_lookup2.saml_attribute == 'title'
711
    assert attribute_lookup2.user_field == 'title'
712

  
713
    set_attribute1, set_attribute2 = authenticator.set_attribute_actions.all().order_by('pk')
714
    assert set_attribute1.saml_attribute == 'http://nice/attribute/givenName'
715
    assert set_attribute1.user_field == 'first_name'
716
    assert set_attribute2.saml_attribute == 'title'
717
    assert set_attribute2.user_field == 'title'
718
-