0001-auth_saml-remove-metadata-file-path-field-70491.patch
src/authentic2_auth_saml/migrations/0013_metadata_file_to_db.py | ||
---|---|---|
1 |
# Generated by Django 2.2.26 on 2022-10-26 10:01 |
|
2 | ||
3 |
import logging |
|
4 |
import os |
|
5 | ||
6 |
from django.db import migrations, transaction |
|
7 | ||
8 |
logger = logging.getLogger('authentic2.auth_saml') |
|
9 | ||
10 | ||
11 |
def metadata_file_to_db(apps, schema_editor): |
|
12 |
SAMLAuthenticator = apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') |
|
13 | ||
14 |
def move_data(authenticator): |
|
15 |
path = authenticator.metadata_path |
|
16 | ||
17 |
if not os.path.exists(path): |
|
18 |
return |
|
19 | ||
20 |
try: |
|
21 |
with open(path) as fd: |
|
22 |
metadata = fd.read() |
|
23 |
except OSError: |
|
24 |
logger.warning('auth_saml: open()/read() call failed for %s on %s', authenticator, path) |
|
25 |
return |
|
26 | ||
27 |
authenticator.metadata = metadata |
|
28 |
authenticator.save() |
|
29 | ||
30 |
for authenticator in SAMLAuthenticator.objects.exclude(metadata_path=''): |
|
31 |
try: |
|
32 |
with transaction.atomic(): |
|
33 |
move_data(authenticator) |
|
34 |
except Exception: |
|
35 |
logger.exception('auth_saml: could not move metadata file to db for %s', authenticator) |
|
36 | ||
37 | ||
38 |
class Migration(migrations.Migration): |
|
39 | ||
40 |
dependencies = [ |
|
41 |
('authentic2_auth_saml', '0012_move_add_role_action'), |
|
42 |
] |
|
43 | ||
44 |
operations = [ |
|
45 |
migrations.RunPython(metadata_file_to_db, reverse_code=migrations.RunPython.noop), |
|
46 |
] |
src/authentic2_auth_saml/migrations/0014_remove_samlauthenticator_metadata_path.py | ||
---|---|---|
1 |
# Generated by Django 2.2.26 on 2022-10-26 10:09 |
|
2 | ||
3 |
from django.db import migrations |
|
4 | ||
5 | ||
6 |
class Migration(migrations.Migration): |
|
7 | ||
8 |
dependencies = [ |
|
9 |
('authentic2_auth_saml', '0013_metadata_file_to_db'), |
|
10 |
] |
|
11 | ||
12 |
operations = [ |
|
13 |
migrations.RemoveField( |
|
14 |
model_name='samlauthenticator', |
|
15 |
name='metadata_path', |
|
16 |
), |
|
17 |
] |
src/authentic2_auth_saml/models.py | ||
---|---|---|
33 | 33 |
metadata_cache_time = models.PositiveSmallIntegerField(_('Metadata cache time'), default=3600) |
34 | 34 |
metadata_http_timeout = models.PositiveSmallIntegerField(_('Metadata HTTP timeout'), default=10) |
35 | 35 | |
36 |
metadata_path = models.CharField( |
|
37 |
_('Metadata file path'), |
|
38 |
max_length=300, |
|
39 |
help_text=_('Absolute path to the IdP metadata file.'), |
|
40 |
blank=True, |
|
41 |
) |
|
42 | 36 |
metadata = models.TextField(_('Metadata (XML)'), blank=True) |
43 | 37 | |
44 | 38 |
provision = models.BooleanField(_('Create user if their username does not already exists'), default=True) |
... | ... | |
143 | 137 |
type = 'saml' |
144 | 138 |
how = ['saml'] |
145 | 139 |
manager_view_template_name = 'authentic2_auth_saml/authenticator_detail.html' |
146 |
description_fields = ['show_condition', 'metadata_url', 'metadata_path', 'metadata', 'provision']
|
|
140 |
description_fields = ['show_condition', 'metadata_url', 'metadata', 'provision'] |
|
147 | 141 | |
148 | 142 |
class Meta: |
149 | 143 |
verbose_name = _('SAML') |
... | ... | |
154 | 148 | |
155 | 149 |
settings['AUTHN_CLASSREF'] = [x.strip() for x in settings['AUTHN_CLASSREF'].split(',') if x.strip()] |
156 | 150 | |
157 |
for setting in ('METADATA', 'METADATA_PATH', 'METADATA_URL'):
|
|
151 |
for setting in ('METADATA', 'METADATA_URL'): |
|
158 | 152 |
if not settings[setting]: |
159 | 153 |
del settings[setting] |
160 | 154 | |
... | ... | |
187 | 181 |
} |
188 | 182 | |
189 | 183 |
def clean(self): |
190 |
if not (self.metadata or self.metadata_path or self.metadata_url):
|
|
184 |
if not (self.metadata or self.metadata_url): |
|
191 | 185 |
raise ValidationError(_('One of the metadata fields must be filled.')) |
192 | 186 | |
193 | 187 |
def autorun(self, request, block_id): |
tests/auth_saml/test_login.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import os |
|
18 | 17 |
from unittest import mock |
19 | 18 | |
20 | 19 |
import pytest |
... | ... | |
57 | 56 | |
58 | 57 | |
59 | 58 |
def test_login_with_conditionnal_authenticators(db, app, settings, caplog): |
60 |
authenticator = SAMLAuthenticator.objects.create( |
|
61 |
enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp1' |
|
62 |
) |
|
59 |
authenticator = SAMLAuthenticator.objects.create(enabled=True, metadata='xxx', slug='idp1') |
|
63 | 60 | |
64 | 61 |
response = app.get('/login/') |
65 | 62 |
assert 'login-saml-idp1' in response |
... | ... | |
69 | 66 |
response = app.get('/login/') |
70 | 67 |
assert 'login-saml-idp1' not in response |
71 | 68 | |
72 |
authenticator2 = SAMLAuthenticator.objects.create( |
|
73 |
enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp2' |
|
74 |
) |
|
69 |
authenticator2 = SAMLAuthenticator.objects.create(enabled=True, metadata='xxx', slug='idp2') |
|
75 | 70 |
response = app.get('/login/') |
76 | 71 |
assert 'login-saml-idp1' not in response |
77 | 72 |
assert 'login-saml-idp2' in response |
... | ... | |
86 | 81 |
def test_login_condition_dnsbl(db, app, settings, caplog): |
87 | 82 |
SAMLAuthenticator.objects.create( |
88 | 83 |
enabled=True, |
89 |
metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'),
|
|
84 |
metadata='xxx',
|
|
90 | 85 |
slug='idp1', |
91 | 86 |
show_condition='remote_addr in dnsbl(\'dnswl.example.com\')', |
92 | 87 |
) |
93 | 88 |
SAMLAuthenticator.objects.create( |
94 | 89 |
enabled=True, |
95 |
metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'),
|
|
90 |
metadata='xxx',
|
|
96 | 91 |
slug='idp2', |
97 | 92 |
show_condition='remote_addr not in dnsbl(\'dnswl.example.com\')', |
98 | 93 |
) |
... | ... | |
105 | 100 |
def test_login_autorun(db, app, settings, patched_adapter): |
106 | 101 |
response = app.get('/login/') |
107 | 102 | |
108 |
authenticator = SAMLAuthenticator.objects.create( |
|
109 |
enabled=True, metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml'), slug='idp1' |
|
110 |
) |
|
103 |
authenticator = SAMLAuthenticator.objects.create(enabled=True, metadata='xxx', slug='idp1') |
|
111 | 104 |
# hide password block |
112 | 105 |
LoginPasswordAuthenticator.objects.update_or_create( |
113 | 106 |
slug='password-authenticator', defaults={'enabled': False} |
tests/auth_saml/test_migrations.py | ||
---|---|---|
400 | 400 |
assert set_attribute1.user_field == 'first_name' |
401 | 401 |
assert set_attribute2.saml_attribute == 'title' |
402 | 402 |
assert set_attribute2.user_field == 'title' |
403 | ||
404 | ||
405 |
def test_saml_authenticator_data_migration_metadata_file_to_db(migration, settings): |
|
406 |
migrate_from = [('authentic2_auth_saml', '0012_move_add_role_action')] |
|
407 |
migrate_to = [('authentic2_auth_saml', '0014_remove_samlauthenticator_metadata_path')] |
|
408 | ||
409 |
old_apps = migration.before(migrate_from) |
|
410 |
SAMLAuthenticator = old_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') |
|
411 | ||
412 |
SAMLAuthenticator.objects.create( |
|
413 |
slug='idp1', metadata_path=os.path.join(os.path.dirname(__file__), 'metadata.xml') |
|
414 |
) |
|
415 | ||
416 |
SAMLAuthenticator.objects.create(slug='idp2', metadata='xxx') |
|
417 |
SAMLAuthenticator.objects.create(slug='idp3', metadata_url='https://example.com') |
|
418 |
SAMLAuthenticator.objects.create(slug='idp4', metadata_path='/unknown/') |
|
419 | ||
420 |
new_apps = migration.apply(migrate_to) |
|
421 |
SAMLAuthenticator = new_apps.get_model('authentic2_auth_saml', 'SAMLAuthenticator') |
|
422 | ||
423 |
authenticator = SAMLAuthenticator.objects.get(slug='idp1') |
|
424 |
assert authenticator.metadata.startswith('<?xml version="1.0"?>') |
|
425 |
assert authenticator.metadata.endswith('</EntityDescriptor>\n') |
|
426 | ||
427 |
assert SAMLAuthenticator.objects.filter(slug='idp2', metadata='xxx').count() == 1 |
|
428 |
assert SAMLAuthenticator.objects.filter(slug='idp3', metadata_url='https://example.com').count() == 1 |
tests/auth_saml/test_misc.py | ||
---|---|---|
49 | 49 |
) |
50 | 50 | |
51 | 51 |
assert 'METADATA' in authenticator.settings |
52 |
assert 'METADATA_PATH' not in authenticator.settings |
|
53 | 52 |
assert 'METADATA_URL' not in authenticator.settings |
54 | 53 |
assert authenticator.settings['AUTHN_CLASSREF'] == ['a', 'b'] |
55 | 54 | |
56 | 55 |
authenticator.metadata = '' |
57 |
authenticator.metadata_path = '/some/path/metadata.xml'
|
|
56 |
authenticator.metadata_url = 'https://example.com/metadata.xml'
|
|
58 | 57 |
authenticator.save() |
59 | 58 | |
60 |
assert 'METADATA_PATH' in authenticator.settings
|
|
59 |
assert 'METADATA_URL' in authenticator.settings
|
|
61 | 60 |
assert 'METADATA' not in authenticator.settings |
62 |
assert 'METADATA_URL' not in authenticator.settings |
|
63 | 61 | |
64 | 62 |
authenticator.authn_classref = '' |
65 | 63 |
authenticator.save() |
tests/test_manager_authenticators.py | ||
---|---|---|
444 | 444 |
resp = resp.form.submit() |
445 | 445 |
assert 'One of the metadata fields must be filled.' in resp.text |
446 | 446 | |
447 |
resp.form['metadata_path'] = '/var/lib/authentic2/metadata.xml'
|
|
447 |
resp.form['metadata_url'] = 'https://example.com/metadata.xml'
|
|
448 | 448 |
resp = resp.form.submit().follow() |
449 |
assert 'Metadata file path: /var/lib/authentic2/metadata.xml' in resp.text
|
|
449 |
assert 'Metadata URL: https://example.com/metadata.xml' in resp.text
|
|
450 | 450 | |
451 | 451 |
resp = resp.click('Enable').follow() |
452 | 452 |
assert 'Authenticator has been enabled.' in resp.text |
... | ... | |
472 | 472 |
assert 'Metadata cache time' not in resp.text |
473 | 473 |
assert 'Metadata HTTP timeout' not in resp.text |
474 | 474 | |
475 |
resp.form['metadata_path'] = '' |
|
476 | 475 |
resp.form['metadata_url'] = 'https://example.com/metadata.xml' |
477 | 476 |
resp = resp.form.submit().follow() |
478 | 477 | |
479 |
- |