Projet

Général

Profil

0008-auth_saml-add-views-to-configure-related-objects-670.patch

Valentin Deniaud, 17 août 2022 11:43

Télécharger (27,1 ko)

Voir les différences:

Subject: [PATCH 8/8] auth_saml: add views to configure related objects
 (#67025)

 src/authentic2/apps/authenticators/models.py  |  1 +
 .../authenticators/authenticator_detail.html  | 24 ++++-
 src/authentic2/apps/authenticators/views.py   |  3 +-
 src/authentic2_auth_saml/forms.py             | 12 +++
 .../journal_event_types.py                    | 93 +++++++++++++++++++
 src/authentic2_auth_saml/models.py            | 32 ++++++-
 .../authenticator_detail.html                 | 27 ++++++
 .../related_object_list.html                  | 11 +++
 src/authentic2_auth_saml/urls.py              | 27 ++++++
 src/authentic2_auth_saml/views.py             | 93 ++++++++++++++++++-
 tests/test_manager_authenticators.py          | 72 ++++++++++++++
 tests/test_manager_journal.py                 | 43 +++++++++
 12 files changed, 430 insertions(+), 8 deletions(-)
 create mode 100644 src/authentic2_auth_saml/journal_event_types.py
 create mode 100644 src/authentic2_auth_saml/templates/authentic2_auth_saml/authenticator_detail.html
 create mode 100644 src/authentic2_auth_saml/templates/authentic2_auth_saml/related_object_list.html
src/authentic2/apps/authenticators/models.py
75 75

  
76 76
    type = ''
77 77
    manager_form_class = None
78
    manager_view_template_name = 'authentic2/authenticators/authenticator_detail.html'
78 79
    unique = False
79 80
    protected = False
80 81
    description_fields = ['show_condition']
src/authentic2/apps/authenticators/templates/authentic2/authenticators/authenticator_detail.html
31 31
  </div>
32 32
  {% endif %}
33 33

  
34
  <div class='placeholder'>
35
    {% for line in object.get_full_description %}
36
      <p>{{ line }}</p>
37
    {% endfor %}
34
  <div class='section authenticator-detail'>
35
    <div class='pk-tabs'>
36
      <div class="pk-tabs--tab-list" role="tablist">
37
        <button aria-controls="panel-description" aria-selected="true" id="tab-description" role="tab" tabindex="0">{% trans "Description" %}</button>
38
        {% block extra-tab-buttons %}
39
        {% endblock %}
40
      </div>
41

  
42
      <div class="pk-tabs--container">
43
        <div aria-labelledby="tab-description" id="panel-description" role="tabpanel" tabindex="0">
44
          <ul>
45
            {% for line in object.get_full_description %}
46
              <li>{{ line }}</li>
47
            {% endfor %}
48
          </ul>
49
        </div>
50
        {% block extra-tab-list %}
51
        {% endblock %}
52
      </div>
53
    </div>
38 54
  </div>
39 55
{% endblock %}
src/authentic2/apps/authenticators/views.py
64 64

  
65 65

  
66 66
class AuthenticatorDetailView(AuthenticatorsMixin, DetailView):
67
    template_name = 'authentic2/authenticators/authenticator_detail.html'
67
    def get_template_names(self):
68
        return self.object.manager_view_template_name
68 69

  
69 70
    @property
70 71
    def title(self):
src/authentic2_auth_saml/forms.py
16 16

  
17 17
from django import forms
18 18

  
19
from authentic2.a2_rbac.models import Role
20
from authentic2.manager.utils import label_from_role
21

  
19 22
from .models import SAMLAuthenticator
20 23

  
21 24

  
......
23 26
    class Meta:
24 27
        model = SAMLAuthenticator
25 28
        exclude = ('ou',)
29

  
30

  
31
class RoleChoiceField(forms.ModelChoiceField):
32
    def __init__(self, *args, **kwargs):
33
        super().__init__(*args, **kwargs)
34
        self.queryset = Role.objects.exclude(slug__startswith='_')
35

  
36
    def label_from_instance(self, obj):
37
        return label_from_role(obj)
src/authentic2_auth_saml/journal_event_types.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 django.utils.translation import gettext_lazy as _
18

  
19
from authentic2.apps.journal.models import EventTypeDefinition
20
from authentic2.apps.journal.utils import form_to_old_new
21

  
22
from .models import SAMLAuthenticator
23

  
24

  
25
class SAMLAuthenticatorEvents(EventTypeDefinition):
26
    @classmethod
27
    def record(cls, *, user, session, related_object, data=None):
28
        data = data or {}
29
        data.update({'related_object': repr(related_object)})
30
        super().record(user=user, session=session, references=[related_object.authenticator], data=data)
31

  
32

  
33
class SAMLAuthenticatorRelatedObjectCreation(SAMLAuthenticatorEvents):
34
    name = 'authenticator.saml.related_object.creation'
35
    label = _('SAML authenticator related object creation')
36

  
37
    @classmethod
38
    def get_message(cls, event, context):
39
        (authenticator,) = event.get_typed_references(SAMLAuthenticator)
40
        related_object = event.get_data('related_object')
41
        if context != authenticator:
42
            return _('creation of {related_object} in authenticator "{authenticator}"').format(
43
                related_object=related_object, authenticator=authenticator
44
            )
45
        else:
46
            return _('creation of %s') % related_object
47

  
48

  
49
class SAMLAuthenticatorRelatedObjectEdit(SAMLAuthenticatorEvents):
50
    name = 'authenticator.saml.related_object.edit'
51
    label = _('SAML authenticator related object edit')
52

  
53
    @classmethod
54
    def record(cls, *, user, session, form):
55
        super().record(
56
            user=user,
57
            session=session,
58
            related_object=form.instance,
59
            data=form_to_old_new(form),
60
        )
61

  
62
    @classmethod
63
    def get_message(cls, event, context):
64
        (authenticator,) = event.get_typed_references(SAMLAuthenticator)
65
        related_object = event.get_data('related_object')
66
        new = event.get_data('new') or {}
67
        edited_attributes = ', '.join(new) or ''
68
        if context != authenticator:
69
            return _('edit {related_object} in authenticator "{authenticator}" ({change})').format(
70
                related_object=related_object,
71
                authenticator=authenticator,
72
                change=edited_attributes,
73
            )
74
        else:
75
            return _('edit {related_object} ({change})').format(
76
                related_object=related_object, change=edited_attributes
77
            )
78

  
79

  
80
class SAMLAuthenticatorRelatedObjectDeletion(SAMLAuthenticatorEvents):
81
    name = 'authenticator.saml.related_object.deletion'
82
    label = _('SAML authenticator related object deletion')
83

  
84
    @classmethod
85
    def get_message(cls, event, context):
86
        (authenticator,) = event.get_typed_references(SAMLAuthenticator)
87
        related_object = event.get_data('related_object')
88
        if context != authenticator:
89
            return _('deletion of {related_object} in authenticator "{authenticator}"').format(
90
                related_object=related_object, authenticator=authenticator
91
            )
92
        else:
93
            return _('deletion of %s') % related_object
src/authentic2_auth_saml/models.py
21 21

  
22 22
from authentic2.a2_rbac.models import Role
23 23
from authentic2.apps.authenticators.models import BaseAuthenticator
24
from authentic2.manager.utils import label_from_role
24 25
from authentic2.utils.misc import redirect_to_login
25 26

  
26
from . import views
27

  
28 27

  
29 28
class SAMLAuthenticator(BaseAuthenticator):
30 29
    metadata_url = models.URLField(_('Metadata URL'), max_length=300, blank=True)
......
147 146

  
148 147
    type = 'saml'
149 148
    how = ['saml']
149
    manager_view_template_name = 'authentic2_auth_saml/authenticator_detail.html'
150 150
    description_fields = [
151 151
        'show_condition',
152 152
        'metadata_url',
......
195 195
        )
196 196

  
197 197
    def login(self, request, *args, **kwargs):
198
        from . import views
199

  
198 200
        return views.login(request, self, *args, **kwargs)
199 201

  
200 202
    def profile(self, request, *args, **kwargs):
203
        from . import views
204

  
201 205
        return views.profile(request, *args, **kwargs)
202 206

  
203 207

  
......
219 223
        default_related_name = 'rename_attribute_actions'
220 224
        verbose_name = _('Rename an attribute')
221 225

  
226
    def __str__(self):
227
        return '%s → %s' % (self.from_name, self.to_name)
228

  
222 229

  
223 230
class SAMLAttributeLookup(SAMLRelatedObjectBase):
224 231
    user_field = models.CharField(_('User field'), max_length=256)
......
229 236
        default_related_name = 'attribute_lookups'
230 237
        verbose_name = _('Attribute lookup')
231 238

  
239
    def __str__(self):
240
        label = _('"%(saml_attribute)s" (from "%(user_field)s")') % {
241
            'saml_attribute': self.saml_attribute,
242
            'user_field': self.user_field,
243
        }
244
        if self.ignore_case:
245
            label = '%s, %s' % (label, _('case insensitive'))
246
        return label
247

  
232 248
    def as_dict(self):
233 249
        return {
234 250
            'user_field': self.user_field,
......
246 262
        default_related_name = 'set_attribute_actions'
247 263
        verbose_name = _('Set an attribute')
248 264

  
265
    def __str__(self):
266
        label = _('"%(attribute)s" from "%(saml_attribute)s"') % {
267
            'attribute': self.attribute,
268
            'saml_attribute': self.saml_attribute,
269
        }
270
        if self.mandatory:
271
            label = '%s (%s)' % (label, _('mandatory'))
272
        return label
273

  
249 274

  
250 275
class AddRoleAction(SAMLRelatedObjectBase):
251 276
    role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.CASCADE)
......
255 280
    class Meta:
256 281
        default_related_name = 'add_role_actions'
257 282
        verbose_name = _('Add a role')
283

  
284
    def __str__(self):
285
        return label_from_role(self.role)
src/authentic2_auth_saml/templates/authentic2_auth_saml/authenticator_detail.html
1
{% extends 'authentic2/authenticators/authenticator_detail.html' %}
2
{% load i18n %}
3

  
4
{% block extra-tab-buttons %}
5
  <button aria-controls="panel-samlattributelookup" aria-selected="false" id="tab-samlattributelookup" role="tab" tabindex="-1">{% trans "Lookup by attributes" %}</button>
6
  <button aria-controls="panel-renameattributeaction" aria-selected="false" id="tab-renameattributeaction" role="tab" tabindex="-1">{% trans "Rename attributes" %}</button>
7
  <button aria-controls="panel-setattributeaction" aria-selected="false" id="tab-setattributeaction" role="tab" tabindex="-1">{% trans "Set attributes" %}</button>
8
  <button aria-controls="panel-addroleaction" aria-selected="false" id="tab-addroleaction" role="tab" tabindex="-1">{% trans "Add roles" %}</button>
9
{% endblock %}
10

  
11
{% block extra-tab-list %}
12
  <div aria-labelledby="tab-samlattributelookup" hidden="" id="panel-samlattributelookup" role="tabpanel" tabindex="0">
13
    {% include 'authentic2_auth_saml/related_object_list.html' with object_list=object.attribute_lookups.all model_name='samlattributelookup' %}
14
  </div>
15

  
16
  <div aria-labelledby="tab-renameattributeaction" hidden="" id="panel-renameattributeaction" role="tabpanel" tabindex="0">
17
    {% include 'authentic2_auth_saml/related_object_list.html' with object_list=object.rename_attribute_actions.all model_name='renameattributeaction' %}
18
  </div>
19

  
20
  <div aria-labelledby="tab-setattributeaction" hidden="" id="panel-setattributeaction" role="tabpanel" tabindex="0">
21
    {% include 'authentic2_auth_saml/related_object_list.html' with object_list=object.set_attribute_actions.all model_name='setattributeaction' %}
22
  </div>
23

  
24
  <div aria-labelledby="tab-addroleaction" hidden="" id="panel-addroleaction" role="tabpanel" tabindex="0">
25
    {% include 'authentic2_auth_saml/related_object_list.html' with object_list=object.add_role_actions.all model_name='addroleaction' %}
26
  </div>
27
{% endblock %}
src/authentic2_auth_saml/templates/authentic2_auth_saml/related_object_list.html
1
{% load i18n %}
2

  
3
<ul class="objects-list single-links">
4
  {% for related_object in object_list %}
5
    <li>
6
      <a rel="popup" href="{% url 'a2-manager-saml-edit-related-object' authenticator_pk=object.pk model_name=model_name pk=related_object.pk %}">{{ related_object }}</a>
7
      <a rel="popup" class="delete" href="{% url 'a2-manager-saml-delete-related-object' authenticator_pk=object.pk model_name=model_name pk=related_object.pk %}">{% trans "Remove" %}</a>
8
    </li>
9
  {% endfor %}
10
  <li><a class="add" rel="popup" href="{% url 'a2-manager-saml-add-related-object' authenticator_pk=object.pk model_name=model_name %}">{% trans 'Add' %}</a></li>
11
</ul>
src/authentic2_auth_saml/urls.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
from django.conf.urls import include, url
18
from django.urls import path
19

  
20
from authentic2.apps.authenticators.manager_urls import superuser_login_required
21
from authentic2.decorators import required
22

  
23
from . import views
18 24

  
19 25
urlpatterns = [
20 26
    url(r'^accounts/saml/', include('mellon.urls'), kwargs={'template_base': 'authentic2/base.html'})
21 27
]
28

  
29
urlpatterns += required(
30
    superuser_login_required,
31
    [
32
        path(
33
            'authenticators/<int:authenticator_pk>/<slug:model_name>/add/',
34
            views.add_related_object,
35
            name='a2-manager-saml-add-related-object',
36
        ),
37
        path(
38
            'authenticators/<int:authenticator_pk>/<slug:model_name>/<int:pk>/edit/',
39
            views.edit_related_object,
40
            name='a2-manager-saml-edit-related-object',
41
        ),
42
        path(
43
            'authenticators/<int:authenticator_pk>/<slug:model_name>/<int:pk>/delete/',
44
            views.delete_related_object,
45
            name='a2-manager-saml-delete-related-object',
46
        ),
47
    ],
48
)
src/authentic2_auth_saml/views.py
1
from django.shortcuts import render
1
from django.apps import apps
2
from django.forms.models import modelform_factory
3
from django.http import Http404
4
from django.shortcuts import get_object_or_404, render
2 5
from django.template.loader import render_to_string
6
from django.urls import reverse
7
from django.views.generic import CreateView, DeleteView, UpdateView
3 8
from mellon.utils import get_idp
4 9

  
10
from authentic2.manager.views import MediaMixin, TitleMixin
5 11
from authentic2.utils.misc import redirect_to_login
6 12

  
13
from .forms import RoleChoiceField
14
from .models import (
15
    AddRoleAction,
16
    RenameAttributeAction,
17
    SAMLAttributeLookup,
18
    SAMLAuthenticator,
19
    SetAttributeAction,
20
)
21

  
7 22

  
8 23
def login(request, authenticator, *args, **kwargs):
9 24
    context = kwargs.pop('context', {}).copy()
......
34 49
        user_saml_identifier.idp = get_idp(user_saml_identifier.issuer.entity_id)
35 50
    context['user_saml_identifiers'] = user_saml_identifiers
36 51
    return render_to_string('authentic2_auth_saml/profile.html', context, request=request)
52

  
53

  
54
class SAMLAuthenticatorMixin(MediaMixin, TitleMixin):
55
    allowed_models = (SAMLAttributeLookup, RenameAttributeAction, SetAttributeAction, AddRoleAction)
56

  
57
    def dispatch(self, request, *args, **kwargs):
58
        self.authenticator = get_object_or_404(SAMLAuthenticator, pk=kwargs.get('authenticator_pk'))
59

  
60
        model_name = kwargs.get('model_name')
61
        if model_name not in (x._meta.model_name for x in self.allowed_models):
62
            raise Http404()
63
        self.model = apps.get_model('authentic2_auth_saml', model_name)
64

  
65
        return super().dispatch(request, *args, **kwargs)
66

  
67
    def get_form_class(self):
68
        return modelform_factory(
69
            self.model, exclude=('authenticator',), field_classes={'role': RoleChoiceField}
70
        )
71

  
72
    def get_form_kwargs(self):
73
        kwargs = super().get_form_kwargs()
74
        if not kwargs.get('instance'):
75
            kwargs['instance'] = self.model()
76
        kwargs['instance'].authenticator = self.authenticator
77
        return kwargs
78

  
79
    def get_success_url(self):
80
        return (
81
            reverse('a2-manager-authenticator-detail', kwargs={'pk': self.authenticator.pk})
82
            + '#open:%s' % self.model._meta.model_name
83
        )
84

  
85
    @property
86
    def title(self):
87
        return self.model._meta.verbose_name
88

  
89

  
90
class RelatedObjectAddView(SAMLAuthenticatorMixin, CreateView):
91
    template_name = 'authentic2/manager/form.html'
92

  
93
    def form_valid(self, form):
94
        resp = super().form_valid(form)
95
        self.request.journal.record(
96
            'authenticator.saml.related_object.creation', related_object=form.instance
97
        )
98
        return resp
99

  
100

  
101
add_related_object = RelatedObjectAddView.as_view()
102

  
103

  
104
class RelatedObjectEditView(SAMLAuthenticatorMixin, UpdateView):
105
    template_name = 'authentic2/manager/form.html'
106

  
107
    def form_valid(self, form):
108
        resp = super().form_valid(form)
109
        self.request.journal.record('authenticator.saml.related_object.edit', form=form)
110
        return resp
111

  
112

  
113
edit_related_object = RelatedObjectEditView.as_view()
114

  
115

  
116
class RelatedObjectDeleteView(SAMLAuthenticatorMixin, DeleteView):
117
    template_name = 'authentic2/authenticators/authenticator_delete_form.html'
118
    title = ''
119

  
120
    def delete(self, *args, **kwargs):
121
        self.request.journal.record(
122
            'authenticator.saml.related_object.deletion', related_object=self.get_object()
123
        )
124
        return super().delete(*args, **kwargs)
125

  
126

  
127
delete_related_object = RelatedObjectDeleteView.as_view()
tests/test_manager_authenticators.py
16 16

  
17 17
import pytest
18 18
from django import VERSION as DJ_VERSION
19
from django.utils.html import escape
19 20

  
20 21
from authentic2.a2_rbac.utils import get_default_ou
21 22
from authentic2.apps.authenticators.models import BaseAuthenticator, LoginPasswordAuthenticator
......
279 280
    assert 'Authenticator has been enabled.' in resp.text
280 281

  
281 282

  
283
def test_authenticators_saml_attribute_lookup(app, superuser):
284
    authenticator = SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1')
285
    resp = login(app, superuser, path=authenticator.get_absolute_url())
286

  
287
    resp = resp.click('Add', href='samlattributelookup')
288
    resp.form['user_field'] = 'email'
289
    resp.form['saml_attribute'] = 'mail'
290
    resp = resp.form.submit()
291
    assert_event('authenticator.saml.related_object.creation', user=superuser, session=app.session)
292
    assert '#open:samlattributelookup' in resp.location
293

  
294
    resp = resp.follow()
295
    assert escape('"mail" (from "email")') in resp.text
296

  
297
    resp = resp.click('mail')
298
    resp.form['ignore_case'] = True
299
    resp = resp.form.submit().follow()
300
    assert escape('"mail" (from "email"), case insensitive') in resp.text
301
    assert_event('authenticator.saml.related_object.edit', user=superuser, session=app.session)
302

  
303
    resp = resp.click('Remove', href='samlattributelookup')
304
    resp = resp.form.submit().follow()
305
    assert 'mail' not in resp.text
306
    assert_event('authenticator.saml.related_object.deletion', user=superuser, session=app.session)
307

  
308

  
309
def test_authenticators_saml_rename_attribute(app, superuser):
310
    authenticator = SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1')
311
    resp = login(app, superuser, path=authenticator.get_absolute_url())
312

  
313
    resp = resp.click('Add', href='renameattributeaction')
314
    resp.form['from_name'] = 'a'
315
    resp.form['to_name'] = 'b'
316
    resp = resp.form.submit().follow()
317
    assert 'a → b' in resp.text
318

  
319

  
320
def test_authenticators_saml_set_attribute(app, superuser):
321
    authenticator = SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1')
322
    resp = login(app, superuser, path=authenticator.get_absolute_url())
323

  
324
    resp = resp.click('Add', href='setattributeaction')
325
    resp.form['attribute'] = 'email'
326
    resp.form['saml_attribute'] = 'mail'
327
    resp = resp.form.submit().follow()
328
    assert escape('"email" from "mail"') in resp.text
329

  
330
    resp = resp.click('mail')
331
    resp.form['mandatory'] = True
332
    resp = resp.form.submit().follow()
333
    assert escape('"email" from "mail" (mandatory)') in resp.text
334

  
335

  
336
def test_authenticators_saml_add_role(app, superuser, role_ou1, role_ou2):
337
    authenticator = SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1')
338
    resp = login(app, superuser, path=authenticator.get_absolute_url())
339

  
340
    resp = resp.click('Add', href='addroleaction')
341
    assert [x[2] for x in resp.form['role'].options] == ['---------', 'OU1 - role_ou1', 'OU2 - role_ou2']
342

  
343
    resp.form['role'] = role_ou1.pk
344
    resp = resp.form.submit().follow()
345
    assert 'role_ou1' in resp.text
346

  
347
    resp = resp.click('role_ou1')
348
    resp.form['role'] = role_ou2.pk
349
    resp = resp.form.submit().follow()
350
    assert 'role_ou1' not in resp.text
351
    assert 'role_ou2' in resp.text
352

  
353

  
282 354
def test_authenticators_order(app, superuser):
283 355
    resp = login(app, superuser, path='/manage/authenticators/')
284 356

  
tests/test_manager_journal.py
28 28
from authentic2.custom_user.models import Profile, ProfileType, User
29 29
from authentic2.journal import journal
30 30
from authentic2.models import Service
31
from authentic2_auth_saml.models import RenameAttributeAction, SAMLAuthenticator
31 32

  
32 33
from .utils import login, logout, text_content
33 34

  
......
58 59
    role_agent = Role.objects.create(name="role2", ou=ou)
59 60
    service = Service.objects.create(name="service")
60 61
    authenticator = LoginPasswordAuthenticator.objects.create(slug='test')
62
    saml_authenticator = SAMLAuthenticator.objects.create(slug='saml')
63
    rename_attribute_action = RenameAttributeAction.objects.create(authenticator=saml_authenticator)
61 64

  
62 65
    class EventFactory:
63 66
        date = make_aware(datetime.datetime(2020, 1, 1))
......
301 304
    make('authenticator.enable', user=agent, session=session2, authenticator=authenticator)
302 305
    make('authenticator.disable', user=agent, session=session2, authenticator=authenticator)
303 306
    make('authenticator.deletion', user=agent, session=session2, authenticator=authenticator)
307
    make(
308
        'authenticator.saml.related_object.creation',
309
        user=agent,
310
        session=session2,
311
        related_object=rename_attribute_action,
312
    )
313
    action_edit_form = mock.Mock(spec=['instance', 'initial', 'changed_data', 'cleaned_data'])
314
    action_edit_form.instance = rename_attribute_action
315
    action_edit_form.initial = {'from_name': 'old'}
316
    action_edit_form.changed_data = ['from_name']
317
    action_edit_form.cleaned_data = {'from_name': 'new'}
318
    make('authenticator.saml.related_object.edit', user=agent, session=session2, form=action_edit_form)
319
    make(
320
        'authenticator.saml.related_object.deletion',
321
        user=agent,
322
        session=session2,
323
        related_object=rename_attribute_action,
324
    )
304 325

  
305 326
    # verify we created at least one event for each type
306 327
    assert set(Event.objects.values_list("type__name", flat=True)) == set(_registry)
......
337 358

  
338 359
def test_global_journal(app, superuser, events):
339 360
    response = login(app, user=superuser, path="/manage/")
361
    rename_attribute_action = RenameAttributeAction.objects.get()
340 362

  
341 363
    # remove event about admin login
342 364
    Event.objects.filter(user=superuser).delete()
......
692 714
            'type': 'authenticator.deletion',
693 715
            'user': 'agent',
694 716
        },
717
        {
718
            'message': 'creation of RenameAttributeAction (%s) in authenticator "SAML"'
719
            % rename_attribute_action.pk,
720
            'timestamp': 'Jan. 3, 2020, 8 a.m.',
721
            'type': 'authenticator.saml.related_object.creation',
722
            'user': 'agent',
723
        },
724
        {
725
            'message': 'edit RenameAttributeAction (%s) in authenticator "SAML" (from_name)'
726
            % rename_attribute_action.pk,
727
            'timestamp': 'Jan. 3, 2020, 9 a.m.',
728
            'type': 'authenticator.saml.related_object.edit',
729
            'user': 'agent',
730
        },
731
        {
732
            'message': 'deletion of RenameAttributeAction (%s) in authenticator "SAML"'
733
            % rename_attribute_action.pk,
734
            'timestamp': 'Jan. 3, 2020, 10 a.m.',
735
            'type': 'authenticator.saml.related_object.deletion',
736
            'user': 'agent',
737
        },
695 738
    ]
696 739

  
697 740
    agent_page = response.click('agent', index=1)
698
-