Projet

Général

Profil

0001-auth_saml-prefer-metadata-file-upload-over-metadata-.patch

Valentin Deniaud, 19 septembre 2022 16:51

Télécharger (9,17 ko)

Voir les différences:

Subject: [PATCH] auth_saml: prefer metadata file upload over metadata path
 (#67451)

 src/authentic2/apps/authenticators/models.py  | 18 ++++++++++----
 src/authentic2_auth_saml/forms.py             |  5 ++++
 .../0009_samlauthenticator_metadata_file.py   | 22 +++++++++++++++++
 src/authentic2_auth_saml/models.py            | 21 ++++++++++++++--
 tests/test_manager_authenticators.py          | 24 +++++++++++++++----
 5 files changed, 79 insertions(+), 11 deletions(-)
 create mode 100644 src/authentic2_auth_saml/migrations/0009_samlauthenticator_metadata_file.py
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
-