0001-auth_saml-prefer-metadata-file-upload-over-metadata-.patch
src/authentic2/apps/authenticators/models.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
import datetime |
18 | 18 |
import logging |
19 |
import os |
|
19 | 20 |
import uuid |
20 | 21 | |
21 | 22 |
from django.core.exceptions import ValidationError |
22 | 23 |
from django.db import models |
24 |
from django.db.models.fields.files import FieldFile |
|
23 | 25 |
from django.shortcuts import render, reverse |
24 | 26 |
from django.utils.formats import date_format |
27 |
from django.utils.html import format_html |
|
28 |
from django.utils.safestring import mark_safe |
|
25 | 29 |
from django.utils.text import capfirst |
26 | 30 |
from django.utils.translation import pgettext_lazy |
27 | 31 |
from django.utils.translation import ugettext_lazy as _ |
... | ... | |
115 | 119 | |
116 | 120 |
if isinstance(value, datetime.datetime): |
117 | 121 |
value = date_format(value, 'DATETIME_FORMAT') |
118 | ||
119 |
yield _('%(field)s: %(value)s') % { |
|
120 |
'field': capfirst(self._meta.get_field(field).verbose_name), |
|
121 |
'value': value, |
|
122 |
} |
|
122 |
elif isinstance(value, FieldFile): |
|
123 |
filename = os.path.basename(value.path) |
|
124 |
value = mark_safe(f'<a href={value.url}>{filename}</a>') |
|
125 | ||
126 |
yield format_html( |
|
127 |
_('{field}: {value}'), |
|
128 |
field=capfirst(self._meta.get_field(field).verbose_name), |
|
129 |
value=value, |
|
130 |
) |
|
123 | 131 | |
124 | 132 |
def shown(self, ctx=()): |
125 | 133 |
if not self.show_condition: |
src/authentic2_auth_saml/forms.py | ||
---|---|---|
45 | 45 |
'attribute_mapping', |
46 | 46 |
) |
47 | 47 | |
48 |
def __init__(self, *args, **kwargs): |
|
49 |
super().__init__(*args, **kwargs) |
|
50 |
if not self.initial.get('metadata_path'): |
|
51 |
del self.fields['metadata_path'] |
|
52 | ||
48 | 53 | |
49 | 54 |
class SAMLAuthenticatorAdvancedForm(forms.ModelForm): |
50 | 55 |
class Meta: |
src/authentic2_auth_saml/migrations/0009_samlauthenticator_metadata_file.py | ||
---|---|---|
1 |
# Generated by Django 2.2.26 on 2022-09-19 13:46 |
|
2 | ||
3 |
from django.db import migrations, models |
|
4 | ||
5 |
import authentic2_auth_saml.models |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('authentic2_auth_saml', '0008_auto_20220913_1105'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AddField( |
|
16 |
model_name='samlauthenticator', |
|
17 |
name='metadata_file', |
|
18 |
field=models.FileField( |
|
19 |
blank=True, null=True, upload_to=authentic2_auth_saml.models.metadata_directory_path |
|
20 |
), |
|
21 |
), |
|
22 |
] |
src/authentic2_auth_saml/models.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 uuid |
|
18 | ||
17 | 19 |
from django.conf import settings |
18 | 20 |
from django.contrib.postgres.fields import JSONField |
19 | 21 |
from django.core.exceptions import ValidationError |
... | ... | |
26 | 28 |
from authentic2.utils.misc import redirect_to_login |
27 | 29 | |
28 | 30 | |
31 |
def metadata_directory_path(instance, filename): |
|
32 |
return 'saml_authenticator_%s/%s/metadata.xml' % (instance.pk, uuid.uuid4()) |
|
33 | ||
34 | ||
29 | 35 |
class SAMLAuthenticator(BaseAuthenticator): |
30 | 36 |
metadata_url = models.URLField(_('Metadata URL'), max_length=300, blank=True) |
31 | 37 |
metadata_cache_time = models.PositiveSmallIntegerField(_('Metadata cache time'), default=3600) |
32 | 38 |
metadata_http_timeout = models.PositiveSmallIntegerField(_('Metadata HTTP timeout'), default=10) |
33 | 39 | |
40 |
metadata_file = models.FileField(null=True, blank=True, upload_to=metadata_directory_path) |
|
34 | 41 |
metadata_path = models.CharField( |
35 | 42 |
_('Metadata file path'), |
36 | 43 |
max_length=300, |
... | ... | |
141 | 148 |
type = 'saml' |
142 | 149 |
how = ['saml'] |
143 | 150 |
manager_view_template_name = 'authentic2_auth_saml/authenticator_detail.html' |
144 |
description_fields = ['show_condition', 'metadata_url', 'metadata_path', 'metadata', 'provision'] |
|
151 |
description_fields = [ |
|
152 |
'show_condition', |
|
153 |
'metadata_url', |
|
154 |
'metadata_file', |
|
155 |
'metadata_path', |
|
156 |
'metadata', |
|
157 |
'provision', |
|
158 |
] |
|
145 | 159 | |
146 | 160 |
class Meta: |
147 | 161 |
verbose_name = _('SAML') |
... | ... | |
156 | 170 |
if not settings[setting]: |
157 | 171 |
del settings[setting] |
158 | 172 | |
173 |
if self.metadata_file: |
|
174 |
settings['METADATA_PATH'] = self.metadata_file.path |
|
175 | ||
159 | 176 |
settings['LOOKUP_BY_ATTRIBUTES'] = [lookup.as_dict() for lookup in self.attribute_lookups.all()] |
160 | 177 | |
161 | 178 |
settings['authenticator'] = self |
... | ... | |
171 | 188 |
] |
172 | 189 | |
173 | 190 |
def clean(self): |
174 |
if not (self.metadata or self.metadata_path or self.metadata_url): |
|
191 |
if not (self.metadata or self.metadata_path or self.metadata_url or self.metadata_file):
|
|
175 | 192 |
raise ValidationError(_('One of the metadata fields must be filled.')) |
176 | 193 | |
177 | 194 |
def autorun(self, request, block_id): |
tests/test_manager_authenticators.py | ||
---|---|---|
17 | 17 |
import pytest |
18 | 18 |
from django import VERSION as DJ_VERSION |
19 | 19 |
from django.utils.html import escape |
20 |
from webtest import Upload |
|
20 | 21 | |
21 | 22 |
from authentic2.a2_rbac.utils import get_default_ou |
22 | 23 |
from authentic2.apps.authenticators.models import BaseAuthenticator, LoginPasswordAuthenticator |
... | ... | |
285 | 286 |
authenticator = SAMLAuthenticator.objects.filter(slug='test').get() |
286 | 287 |
resp = app.get(authenticator.get_absolute_url()) |
287 | 288 |
assert 'Create user if their username does not already exists: True' in resp.text |
288 |
assert 'Metadata file path' not in resp.text
|
|
289 |
assert 'Metadata file' not in resp.text |
|
289 | 290 | |
290 | 291 |
assert 'Enable' not in resp.text |
291 | 292 |
assert 'configuration is not complete' in resp.text |
... | ... | |
297 | 298 |
resp = resp.form.submit() |
298 | 299 |
assert 'One of the metadata fields must be filled.' in resp.text |
299 | 300 | |
300 |
resp.form['metadata_path'] = '/var/lib/authentic2/metadata.xml'
|
|
301 |
resp.form['metadata_file'] = Upload('metadata.xml', b'<xml>some-xml</xml>', 'text/xml')
|
|
301 | 302 |
resp = resp.form.submit().follow() |
302 |
assert 'Metadata file path: /var/lib/authentic2/metadata.xml' in resp.text |
|
303 | ||
304 |
authenticator.refresh_from_db() |
|
305 |
assert 'Metadata file: <a href=%s>metadata.xml</a>' % authenticator.metadata_file.url in resp.text |
|
303 | 306 | |
304 | 307 |
resp = resp.click('Enable').follow() |
305 | 308 |
assert 'Authenticator has been enabled.' in resp.text |
... | ... | |
325 | 328 |
assert 'Metadata cache time' not in resp.text |
326 | 329 |
assert 'Metadata HTTP timeout' not in resp.text |
327 | 330 | |
328 |
resp.form['metadata_path'] = '' |
|
329 | 331 |
resp.form['metadata_url'] = 'https://example.com/metadata.xml' |
330 | 332 |
resp = resp.form.submit().follow() |
331 | 333 | |
... | ... | |
347 | 349 |
assert 'Signing key is missing' not in resp.text |
348 | 350 | |
349 | 351 | |
352 |
def test_authenticators_saml_hide_metadata_path(app, superuser): |
|
353 |
authenticator = SAMLAuthenticator.objects.create(slug='idp1') |
|
354 | ||
355 |
resp = login(app, superuser) |
|
356 |
resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk) |
|
357 |
assert 'metadata_path' not in resp.form.fields |
|
358 | ||
359 |
authenticator.metadata_path = '/var/lib/authentic2/metadata.xml' |
|
360 |
authenticator.save() |
|
361 | ||
362 |
resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk) |
|
363 |
assert 'metadata_path' in resp.form.fields |
|
364 | ||
365 | ||
350 | 366 |
def test_authenticators_saml_attribute_lookup(app, superuser): |
351 | 367 |
authenticator = SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1') |
352 | 368 |
resp = login(app, superuser, path=authenticator.get_absolute_url()) |
353 |
- |