Projet

Général

Profil

0003-manager-controle-role-inheritance-using-table-53481.patch

Valentin Deniaud, 02 août 2021 16:43

Télécharger (32,4 ko)

Voir les différences:

Subject: [PATCH 3/3] manager: controle role inheritance using table (#53481)

 src/authentic2/manager/forms.py               |   6 +-
 src/authentic2/manager/role_views.py          | 203 +++++++++---------
 src/authentic2/manager/tables.py              |  21 ++
 .../authentic2/manager/role_members.html      |  20 +-
 .../authentic2/manager/role_remove_child.html |  23 --
 .../manager/role_remove_parent.html           |  23 --
 .../authentic2/manager/roles_inheritance.html |  25 +++
 .../authentic2/manager/user_ou_roles.html     |   2 +-
 src/authentic2/manager/urls.py                |  10 -
 tests/test_manager.py                         | 189 +++++++++-------
 10 files changed, 262 insertions(+), 260 deletions(-)
 delete mode 100644 src/authentic2/manager/templates/authentic2/manager/role_remove_child.html
 delete mode 100644 src/authentic2/manager/templates/authentic2/manager/role_remove_parent.html
 create mode 100644 src/authentic2/manager/templates/authentic2/manager/roles_inheritance.html
src/authentic2/manager/forms.py
133 133

  
134 134

  
135 135
class RolesForm(LimitQuerysetFormMixin, CssClass, forms.Form):
136

  
137 136
    roles = fields.ChooseRolesField(label=_('Add some roles'))
138 137

  
139 138

  
140
class RoleParentsForm(LimitQuerysetFormMixin, CssClass, forms.Form):
141
    roles = fields.ChooseManageableMemberRolesField(label=_('Add some roles'))
139
class RoleParentForm(LimitQuerysetFormMixin, CssClass, forms.Form):
140
    role = fields.ChooseManageableMemberRoleField(label=_('Add some roles'))
141
    action = forms.CharField(initial='add', widget=forms.HiddenInput)
142 142

  
143 143

  
144 144
class ChooseUserRoleForm(LimitQuerysetFormMixin, CssClass, forms.Form):
src/authentic2/manager/role_views.py
21 21
from django.contrib.contenttypes.models import ContentType
22 22
from django.core.exceptions import PermissionDenied, ValidationError
23 23
from django.db import transaction
24
from django.db.models import Count, F
24
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Prefetch
25
from django.db.models.functions import Cast
25 26
from django.db.models.query import Prefetch, Q
26 27
from django.shortcuts import get_object_or_404
27 28
from django.urls import reverse
......
34 35
from authentic2.apps.journal.views import JournalViewWithContext
35 36
from authentic2.forms.profile import modelform_factory
36 37
from authentic2.utils.misc import redirect
37
from django_rbac.utils import get_ou_model, get_permission_model, get_role_model
38
from django_rbac.utils import get_ou_model, get_permission_model, get_role_model, get_role_parenting_model
38 39

  
39 40
from . import app_settings, forms, resources, tables, views
40 41
from .journal_views import BaseJournalView
......
365 366
members_export = RoleMembersExportView.as_view()
366 367

  
367 368

  
368
class RoleAddChildView(
369
    views.AjaxFormViewMixin,
370
    views.TitleMixin,
371
    views.PermissionMixin,
372
    views.FormNeedsRequest,
373
    SingleObjectMixin,
374
    FormView,
375
):
369
class RoleAddChildView(RoleViewMixin, views.HideOUColumnMixin, views.BaseSubTableView):
376 370
    title = _('Add child role')
377
    model = get_role_model()
378
    form_class = forms.RolesForm
379
    success_url = '..'
380
    template_name = 'authentic2/manager/form.html'
371
    form_class = forms.ChooseRoleForm
372
    table_class = tables.InheritanceRolesTable
373
    search_form_class = forms.RoleSearchForm
374
    template_name = 'authentic2/manager/roles_inheritance.html'
381 375
    permissions = ['a2_rbac.manage_members_role']
376
    success_url = '.'
377
    slug_field = 'uuid'
382 378

  
383
    def dispatch(self, request, *args, **kwargs):
384
        self.object = self.get_object()
385
        return super().dispatch(request, *args, **kwargs)
379
    def get_table_queryset(self):
380
        qs = super().get_table_queryset()
381
        qs = qs.exclude(pk=self.object.pk)
382
        children = self.object.children(annotate=True, include_self=False)
383
        children = children.annotate(is_direct=Cast('direct', output_field=BooleanField()))
384
        qs = qs.annotate(
385
            checked=ExpressionWrapper(Q(pk__in=children.filter(is_direct=True)), output_field=BooleanField())
386
        )
387
        qs = qs.annotate(
388
            indeterminate=ExpressionWrapper(
389
                Q(pk__in=children.filter(is_direct=False)), output_field=BooleanField()
390
            )
391
        )
392
        RoleParenting = get_role_parenting_model()
393
        rp_qs = RoleParenting.objects.filter(parent__in=children).annotate(name=F('parent__name'))
394
        qs = qs.prefetch_related(Prefetch('parent_relation', queryset=rp_qs, to_attr='via'))
395
        return qs
386 396

  
387 397
    def form_valid(self, form):
388
        parent = self.get_object()
389
        for role in form.cleaned_data['roles']:
390
            parent.add_child(role)
398
        role = form.cleaned_data['role']
399
        action = form.cleaned_data['action']
400
        if action == 'add':
401
            self.object.add_child(role)
402
            hooks.call_hooks(
403
                'event', name='manager-add-child-role', user=self.request.user, parent=self.object, child=role
404
            )
405
            self.request.journal.record('manager.role.inheritance.addition', parent=self.object, child=role)
406
        elif action == 'remove':
407
            self.object.remove_child(role)
391 408
            hooks.call_hooks(
392
                'event', name='manager-add-child-role', user=self.request.user, parent=parent, child=role
409
                'event',
410
                name='manager-remove-child-role',
411
                user=self.request.user,
412
                parent=self.object,
413
                child=role,
393 414
            )
394
            self.request.journal.record('manager.role.inheritance.addition', parent=parent, child=role)
415
            self.request.journal.record('manager.role.inheritance.removal', parent=self.object, child=role)
395 416
        return super().form_valid(form)
396 417

  
418
    def get_search_form_kwargs(self):
419
        kwargs = super().get_search_form_kwargs()
420
        kwargs['queryset'] = self.request.user.filter_by_perm(
421
            'a2_rbac.view_role', get_role_model().objects.all()
422
        )
423
        return kwargs
424

  
397 425

  
398 426
add_child = RoleAddChildView.as_view()
399 427

  
400 428

  
401
class RoleAddParentView(
402
    views.AjaxFormViewMixin, views.TitleMixin, views.FormNeedsRequest, SingleObjectMixin, FormView
403
):
429
class RoleAddParentView(RoleViewMixin, views.HideOUColumnMixin, views.BaseSubTableView):
404 430
    title = _('Add parent role')
405
    model = get_role_model()
406
    form_class = forms.RoleParentsForm
407
    success_url = '..'
408
    template_name = 'authentic2/manager/form.html'
431
    form_class = forms.RoleParentForm
432
    table_class = tables.InheritanceRolesTable
433
    search_form_class = forms.RoleSearchForm
434
    template_name = 'authentic2/manager/roles_inheritance.html'
435
    success_url = '.'
436
    slug_field = 'uuid'
409 437

  
410 438
    def dispatch(self, request, *args, **kwargs):
411
        self.object = self.get_object()
412
        if self.object.is_internal():
439
        if self.get_object().is_internal():
413 440
            raise PermissionDenied
414 441
        return super().dispatch(request, *args, **kwargs)
415 442

  
443
    def get_table_queryset(self):
444
        qs = super().get_table_queryset()
445
        qs = self.request.user.filter_by_perm('a2_rbac.manage_members_role', qs)
446
        qs = qs.exclude(pk=self.object.pk)
447
        parents = self.object.parents(annotate=True, include_self=False)
448
        parents = parents.annotate(is_direct=Cast('direct', output_field=BooleanField()))
449
        qs = qs.annotate(
450
            checked=ExpressionWrapper(Q(pk__in=parents.filter(is_direct=True)), output_field=BooleanField())
451
        )
452
        qs = qs.annotate(
453
            indeterminate=ExpressionWrapper(
454
                Q(pk__in=parents.filter(is_direct=False)), output_field=BooleanField()
455
            )
456
        )
457
        RoleParenting = get_role_parenting_model()
458
        rp_qs = RoleParenting.objects.filter(child__in=parents).annotate(name=F('child__name'))
459
        qs = qs.prefetch_related(Prefetch('child_relation', queryset=rp_qs, to_attr='via'))
460
        return qs
461

  
416 462
    def form_valid(self, form):
417
        child = self.get_object()
418
        for role in form.cleaned_data['roles']:
419
            child.add_parent(role)
463
        role = form.cleaned_data['role']
464
        action = form.cleaned_data['action']
465
        if action == 'add':
466
            self.object.add_parent(role)
420 467
            hooks.call_hooks(
421
                'event', name='manager-add-child-role', user=self.request.user, parent=role, child=child
468
                'event', name='manager-add-child-role', user=self.request.user, parent=role, child=self.object
469
            )
470
            self.request.journal.record('manager.role.inheritance.addition', parent=role, child=self.object)
471
        elif action == 'remove':
472
            self.object.remove_parent(role)
473
            hooks.call_hooks(
474
                'event',
475
                name='manager-remove-child-role',
476
                user=self.request.user,
477
                parent=role,
478
                child=self.object,
422 479
            )
423
            self.request.journal.record('manager.role.inheritance.addition', parent=role, child=child)
480
            self.request.journal.record('manager.role.inheritance.removal', parent=role, child=self.object)
424 481
        return super().form_valid(form)
425 482

  
426

  
427
add_parent = RoleAddParentView.as_view()
428

  
429

  
430
class RoleRemoveChildView(views.AjaxFormViewMixin, SingleObjectMixin, views.PermissionMixin, TemplateView):
431
    title = _('Remove child role')
432
    model = get_role_model()
433
    success_url = '../..'
434
    template_name = 'authentic2/manager/role_remove_child.html'
435
    permissions = ['a2_rbac.manage_members_role']
436

  
437
    def dispatch(self, request, *args, **kwargs):
438
        self.object = self.get_object()
439
        self.child = self.get_queryset().get(pk=kwargs['child_pk'])
440
        return super().dispatch(request, *args, **kwargs)
441

  
442
    def get_context_data(self, **kwargs):
443
        ctx = super().get_context_data(**kwargs)
444
        ctx['child'] = self.child
445
        return ctx
446

  
447
    def post(self, request, *args, **kwargs):
448
        self.object.remove_child(self.child)
449
        hooks.call_hooks(
450
            'event',
451
            name='manager-remove-child-role',
452
            user=self.request.user,
453
            parent=self.object,
454
            child=self.child,
455
        )
456
        self.request.journal.record('manager.role.inheritance.removal', parent=self.object, child=self.child)
457
        return redirect(self.request, self.success_url)
458

  
459

  
460
remove_child = RoleRemoveChildView.as_view()
461

  
462

  
463
class RoleRemoveParentView(views.AjaxFormViewMixin, SingleObjectMixin, TemplateView):
464
    title = _('Remove parent role')
465
    model = get_role_model()
466
    success_url = '../..'
467
    template_name = 'authentic2/manager/role_remove_parent.html'
468

  
469
    def dispatch(self, request, *args, **kwargs):
470
        self.object = self.get_object()
471
        if self.object.is_internal():
472
            raise PermissionDenied
473
        self.parent = self.get_queryset().get(pk=kwargs['parent_pk'])
474
        return super().dispatch(request, *args, **kwargs)
475

  
476
    def get_context_data(self, **kwargs):
477
        ctx = super().get_context_data(**kwargs)
478
        ctx['parent'] = self.parent
479
        return ctx
480

  
481
    def post(self, request, *args, **kwargs):
482
        if not self.request.user.has_perm('a2_rbac.manage_members_role', self.parent):
483
            raise PermissionDenied
484
        self.object.remove_parent(self.parent)
485
        hooks.call_hooks(
486
            'event',
487
            name='manager-remove-child-role',
488
            user=self.request.user,
489
            parent=self.parent,
490
            child=self.object,
483
    def get_search_form_kwargs(self):
484
        kwargs = super().get_search_form_kwargs()
485
        kwargs['queryset'] = self.request.user.filter_by_perm(
486
            'a2_rbac.manage_members_role', get_role_model().objects.all()
491 487
        )
492
        self.request.journal.record('manager.role.inheritance.removal', parent=self.parent, child=self.object)
493
        return redirect(self.request, self.success_url)
488
        return kwargs
494 489

  
495 490

  
496
remove_parent = RoleRemoveParentView.as_view()
491
add_parent = RoleAddParentView.as_view()
497 492

  
498 493

  
499 494
class RoleAddAdminRoleView(
src/authentic2/manager/tables.py
240 240
        attrs = {'class': 'main plaintable', 'id': 'user-authorizations-table'}
241 241
        fields = ('client', 'created', 'expired')
242 242
        empty_text = _('This user has not granted profile data access to any service yet.')
243

  
244

  
245
class InheritanceRolesTable(tables.Table):
246
    name = tables.LinkColumn(
247
        viewname='a2-manager-role-members', kwargs={'pk': A('pk')}, accessor='name', verbose_name=_('label')
248
    )
249
    via = tables.TemplateColumn(
250
        '''{% for rel in record.via %}{{ rel.name }}{% if not forloop.last %}, {% endif %}{% endfor %}''',
251
        verbose_name=_('Inherited from'),
252
        orderable=False,
253
    )
254
    member = tables.TemplateColumn(
255
        '<input class="role-member{% if record.indeterminate %} indeterminate{% endif %}" name="role-{{ record.pk }}" type="checkbox" {% if record.checked %}checked{% endif %}/>',
256
        verbose_name='',
257
    )
258

  
259
    class Meta:
260
        model = get_role_model()
261
        attrs = {'class': 'main plaintable', 'id': 'inheritance-role-table'}
262
        fields = ('name', 'ou')
263
        empty_text = _('None')
src/authentic2/manager/templates/authentic2/manager/role_members.html
109 109
   {% trans "Child roles:" %}
110 110
   {% for child in children %}
111 111
     <a href="{% url "a2-manager-role-members" pk=child.pk %}">{{ child }}</a>
112
     {% if child.direct %}
113
       <a rel="popup" href="{% url "a2-manager-role-remove-child" pk=object.pk child_pk=child.pk %}" class="role-remove icon-minus-sign"></a>
114
     {% else %}
115
       <a title="{% trans "Indirect child role" %}" class="disabled role-remove icon-minus-sign"></a>
116
     {% endif %}
112
     {% if not forloop.last %} − {% endif %}
117 113
   {% endfor %}
118 114
  {% if view.can_manage_members %}
119
    <a rel="popup" href="{% url "a2-manager-role-add-child" pk=object.pk %}" class="role-add icon-add-sign"></a>
115
    <a href="{% url "a2-manager-role-add-child" pk=object.pk %}" class="role-add icon-add-sign"></a>
120 116
  {% else %}
121 117
    <a title="{% trans "Permission denied" %}" class="disabled role-add icon-add-sign"></a>
122 118
  {% endif %}
......
127 123
     <a class="role" href="{% url "a2-manager-role-members" pk=parent.pk %}">
128 124
       {% if parent.ou and has_multiple_ou %}{{ parent.ou }} - {% endif %}{{ parent }}
129 125
     </a>
130
     {% if parent.direct %}
131
       {% if not object.is_internal %}
132
         <a rel="popup" href="{% url "a2-manager-role-remove-parent" pk=object.pk parent_pk=parent.pk %}" class="role-remove icon-minus-sign"></a>
133
       {% else %}
134
         <a title="{% trans "This role is technical, you cannot modify its permissions." %}" class="disabled role-add icon-minus-sign"></a>
135
       {% endif %}
136
     {% else %}
137
       <a title="{% trans "Indirect parent role" %}" class="disabled role-remove icon-minus-sign"></a>
138
     {% endif %}
126
    {% if not forloop.last %} − {% endif %}
139 127
   {% endfor %}
140 128
   {% if not object.is_internal %}
141
     <a rel="popup" href="{% url "a2-manager-role-add-parent" pk=object.pk %}" class="role-add icon-add-sign"></a>
129
     <a href="{% url "a2-manager-role-add-parent" pk=object.pk %}" class="role-add icon-add-sign"></a>
142 130
   {% else %}
143 131
     <a title="{% trans "This role is technical, you cannot modify its permissions." %}" class="disabled role-add icon-add-sign"></a>
144 132
   {% endif %}
src/authentic2/manager/templates/authentic2/manager/role_remove_child.html
1
{% extends "authentic2/manager/base.html" %}
2
{% load i18n %}
3

  
4
{% block messages %}
5
{% endblock %}
6

  
7
{% block main %}
8
  {% if title %}
9
    <div id="appbar"><h2>{{ title }}</h2></div>
10
  {% endif %}
11
  <form method="post">
12
    {% csrf_token %}
13
    <div class="form-inner-container">
14
      {% block caption %}
15
      <p>{% blocktrans %}Do you want to remove child role {{ child }} ?{% endblocktrans %}</p>
16
      {% endblock %}
17
      <div class="buttons">
18
        <button>{% trans "Remove" %}</button>
19
        <a class="cancel" href="..">{% trans "Cancel" %}</a>
20
      </div>
21
    </div>
22
  </form>
23
{% endblock %}
src/authentic2/manager/templates/authentic2/manager/role_remove_parent.html
1
{% extends "authentic2/manager/base.html" %}
2
{% load i18n %}
3

  
4
{% block messages %}
5
{% endblock %}
6

  
7
{% block main %}
8
  {% if title %}
9
    <div id="appbar"><h2>{{ title }}</h2></div>
10
  {% endif %}
11
  <form method="post">
12
    {% csrf_token %}
13
    <div class="form-inner-container">
14
      {% block caption %}
15
      <p>{% blocktrans %}Do you want to remove parent role {{ parent }} ?{% endblocktrans %}</p>
16
      {% endblock %}
17
      <div class="buttons">
18
        <button>{% trans "Remove" %}</button>
19
        <a class="cancel" href="..">{% trans "Cancel" %}</a>
20
      </div>
21
    </div>
22
  </form>
23
{% endblock %}
src/authentic2/manager/templates/authentic2/manager/roles_inheritance.html
1
{% extends "authentic2/manager/role_common.html" %}
2
{% load i18n static django_tables2 %}
3

  
4
{% block breadcrumb %}
5
  {{ block.super }}
6
  <a href="..">{{ object }}</a>
7
  <a href="#">{% trans "Role inheritance" %}</a>
8
{% endblock %}
9

  
10
{% block extrascripts %}
11
  {{ block.super }}
12
  <script src="{% static "authentic2/manager/js/roles_ajax_checkbox.js" %}"></script>
13
{% endblock %}
14

  
15
{% block main %}
16
 {% with row_link=0 %}
17
   {% render_table table "authentic2/manager/table.html" %}
18
 {% endwith %}
19
{% endblock %}
20

  
21
{% block sidebar %}
22
  <aside id="sidebar">
23
    {% include "authentic2/manager/search_form.html" %}
24
  </aside>
25
{% endblock %}
src/authentic2/manager/templates/authentic2/manager/user_ou_roles.html
1 1
{% extends "authentic2/manager/user_common_roles.html" %}
2
{% load django_tables2 %}
2
{% load static django_tables2 %}
3 3

  
4 4
{% block extrascripts %}
5 5
  {{ block.super }}
src/authentic2/manager/urls.py
128 128
        url(r'^roles/(?P<pk>\d+)/$', role_views.members, name='a2-manager-role-members'),
129 129
        url(r'^roles/(?P<pk>\d+)/add-child/$', role_views.add_child, name='a2-manager-role-add-child'),
130 130
        url(r'^roles/(?P<pk>\d+)/add-parent/$', role_views.add_parent, name='a2-manager-role-add-parent'),
131
        url(
132
            r'^roles/(?P<pk>\d+)/remove-child/(?P<child_pk>\d+)/$',
133
            role_views.remove_child,
134
            name='a2-manager-role-remove-child',
135
        ),
136
        url(
137
            r'^roles/(?P<pk>\d+)/remove-parent/(?P<parent_pk>\d+)/$',
138
            role_views.remove_parent,
139
            name='a2-manager-role-remove-parent',
140
        ),
141 131
        url(
142 132
            r'^roles/(?P<pk>\d+)/add-admin-user/$',
143 133
            role_views.add_admin_user,
tests/test_manager.py
861 861

  
862 862

  
863 863
def test_roles_for_change_widget(admin, app, db):
864
    from authentic2.manager.forms import RoleParentsForm
864
    from authentic2.manager.forms import RoleParentForm
865 865

  
866 866
    login(app, admin, '/manage/')
867 867
    Role.objects.create(name='admin 1')
868 868
    Role.objects.create(name='user 1')
869 869

  
870
    form = RoleParentsForm(request=None)
870
    form = RoleParentForm(request=None)
871 871
    assert form.as_p()
872
    field_id = form.fields['roles'].widget.build_attrs({})['data-field_id']
872
    field_id = form.fields['role'].widget.build_attrs({})['data-field_id']
873 873
    url = reverse('django_select2-json')
874 874
    response = app.get(url, params={'field_id': field_id, 'term': 'admin'})
875 875
    assert len(response.json['results']) == 1
......
940 940
    simple_role.permissions.add(view_role_perm)
941 941
    simple_user.roles.add(simple_role)
942 942
    admin.roles.add(role)
943

  
943 944
    response = app.get('/manage/roles/%s/add-child/' % simple_role.pk)
944
    form = response.form
945
    form['roles'].force_value(role.pk)
946
    form.submit().follow()
945
    token = str(response.context['csrf_token'])
946
    params = {'action': 'add', 'role': role.pk, 'csrfmiddlewaretoken': token}
947
    response = app.post('/manage/roles/%s/add-child/' % simple_role.pk, params=params)
947 948
    assert role in simple_role.children()
948 949

  
949
    url = '/manage/roles/%s/remove-child/%s/' % (simple_role.pk, role.pk)
950
    token = str(response.context['csrf_token'])
951
    app.post(url, params={'csrfmiddlewaretoken': token})
952
    assert not role in simple_role.children()
950
    params = {'action': 'remove', 'role': role.pk, 'csrfmiddlewaretoken': token}
951
    response = app.post('/manage/roles/%s/add-child/' % simple_role.pk, params=params)
952
    assert role not in simple_role.children()
953 953

  
954 954
    response = app.get('/manage/roles/%s/add-parent/' % role.pk)
955
    form = response.form
956
    form['roles'].force_value(simple_role.pk)
957
    form.submit().follow()
955
    token = str(response.context['csrf_token'])
956
    params = {'action': 'add', 'role': simple_role.pk, 'csrfmiddlewaretoken': token}
957
    response = app.post('/manage/roles/%s/add-parent/' % role.pk, params=params)
958 958
    assert simple_role in role.parents()
959 959

  
960
    url = '/manage/roles/%s/remove-parent/%s/' % (role.pk, simple_role.pk)
961
    token = str(response.context['csrf_token'])
962
    app.post(url, params={'csrfmiddlewaretoken': token})
960
    params = {'action': 'remove', 'role': simple_role.pk, 'csrfmiddlewaretoken': token}
961
    response = app.post('/manage/roles/%s/add-parent/' % role.pk, params=params)
963 962
    assert simple_role not in role.parents()
964 963

  
964
    # try to add arbitrary role
965
    admin_role = Role.objects.get(slug='_a2-manager')
966
    response = app.get('/manage/roles/%s/add-parent/' % role.pk)
967
    token = str(response.context['csrf_token'])
968
    params = {'action': 'add', 'role': admin_role.pk, 'csrfmiddlewaretoken': token}
969
    response = app.post('/manage/roles/%s/add-parent/' % simple_role.pk, params=params)
970
    assert admin_role not in role.parents()
971

  
965 972
    # user roles view works
966 973
    response = app.get('/manage/users/%s/roles/' % admin.pk)
967 974
    q = response.pyquery.remove_namespaces()
......
978 985
    app.get('/manage/roles/%s/delete/' % simple_role.pk, status=403)
979 986

  
980 987

  
981
def test_manager_permission_inheritance(app, simple_user, admin, simple_role):
982
    admin_role = Role.objects.get(slug='_a2-manager')
983
    view_role_perm = get_permission_model().objects.create(
984
        operation=get_operation(VIEW_OP),
985
        target_ct=ContentType.objects.get_for_model(Role),
986
        target_id=simple_role.pk,
987
    )
988
    simple_role.permissions.add(view_role_perm)
989
    simple_user.roles.add(simple_role)
990
    login(app, simple_user, '/manage/')
991

  
992
    response = app.get('/manage/roles/%s/add-parent/' % simple_role.pk)
993
    form = response.form
994
    form['roles'].force_value(admin_role.pk)
995
    response = form.submit()
996

  
997
    assert response.status_code == 200
998
    assert not admin_role in simple_role.parents()
999

  
1000

  
1001 988
def test_manager_widget_fields_validation(app, simple_user, simple_role):
1002 989
    '''Verify that fields corresponding to widget implement queryset restrictions.'''
1003 990
    from authentic2.manager.forms import (
1004 991
        ChooseRoleForm,
1005 992
        ChooseUserForm,
1006 993
        ChooseUserRoleForm,
1007
        RoleParentsForm,
994
        RoleParentForm,
1008 995
        RolesForm,
1009 996
        UsersForm,
1010 997
    )
......
1056 1043
    assert error_message in form.errors['roles'][0]
1057 1044

  
1058 1045
    # For those we need manage_members permission
1059
    form = RoleParentsForm(request=request, data={'roles': [visible_role.pk]})
1060
    assert error_message in form.errors['roles'][0]
1046
    form = RoleParentForm(request=request, data={'role': visible_role.pk, 'action': 'add'})
1047
    assert error_message in form.errors['role'][0]
1061 1048

  
1062 1049
    form = ChooseUserRoleForm(request=request, data={'role': visible_role.pk, 'action': 'add'})
1063 1050
    assert error_message in form.errors['role'][0]
......
1070 1057
    simple_role.permissions.add(change_role_perm)
1071 1058
    del simple_user._rbac_perms_cache
1072 1059

  
1073
    form = RoleParentsForm(request=request, data={'roles': [visible_role.pk]})
1060
    form = RoleParentForm(request=request, data={'role': visible_role.pk, 'action': 'add'})
1074 1061
    assert form.is_valid()
1075 1062

  
1076 1063
    form = ChooseUserRoleForm(request=request, data={'role': visible_role.pk, 'action': 'add'})
1077 1064
    assert form.is_valid()
1078 1065

  
1079 1066

  
1080
def test_manager_role_widgets_choices(app, simple_user, simple_role):
1081
    def get_choices(response):
1082
        select2_json = request_select2(app, response)
1083
        assert select2_json['more'] is False
1084
        return {result['id'] for result in select2_json['results']}
1067
@pytest.mark.parametrize('relation', ['child', 'parent'])
1068
def test_manager_role_inheritance_list(app, admin, simple_role, ou1, relation):
1069
    first_role = Role.objects.create(name='first_role', ou=simple_role.ou)
1070
    second_role = Role.objects.create(name='second_role', ou=simple_role.ou)
1071
    third_role = Role.objects.create(name='third_role', ou=ou1)
1085 1072

  
1073
    if relation == 'child':
1074
        simple_role.add_child(first_role)
1075
        first_role.add_child(second_role)
1076
    elif relation == 'parent':
1077
        simple_role.add_parent(first_role)
1078
        first_role.add_parent(second_role)
1079

  
1080
    response = login(app, admin)
1081
    response = app.get('/manage/roles/%s/add-%s/' % (simple_role.pk, relation))
1082
    q = response.pyquery.remove_namespaces()
1083
    assert len(q('table tbody tr')) == 3
1084
    assert {e.text_content() for e in q('table tbody td.name')} == {
1085
        first_role.name,
1086
        second_role.name,
1087
        third_role.name,
1088
    }
1089

  
1090
    row = q('table tbody tr')[0]
1091
    name, ou, via, member = row.getchildren()
1092
    assert name.text_content() == 'first_role'
1093
    assert ou.text_content() == 'Default organizational unit'
1094
    assert not via.text_content()
1095
    member = member.find('input')
1096
    assert member.checked
1097
    assert member.attrib['class'] == 'role-member'
1098

  
1099
    row = q('table tbody tr')[1]
1100
    name, ou, via, member = row.getchildren()
1101
    assert name.text_content() == 'second_role'
1102
    assert ou.text_content() == 'Default organizational unit'
1103
    assert via.text_content() == 'first_role'
1104
    member = member.find('input')
1105
    assert not member.checked
1106
    assert member.attrib['class'] == 'role-member indeterminate'
1107

  
1108
    row = q('table tbody tr')[2]
1109
    name, ou, via, member = row.getchildren()
1110
    assert name.text_content() == 'third_role'
1111
    assert ou.text_content() == 'OU1'
1112
    assert not via.text_content()
1113
    member = member.find('input')
1114
    assert not member.checked
1115
    assert member.attrib['class'] == 'role-member'
1116

  
1117

  
1118
def test_manager_role_inheritance_list_search_permission(app, admin, simple_user, simple_role, ou1):
1086 1119
    visible_role = Role.objects.create(name='visible_role', ou=simple_user.ou)
1087
    Role.objects.create(name='invisible_role', ou=simple_user.ou)
1120
    visible_role_2 = Role.objects.create(name='visible_role_2', ou=ou1)
1121
    invisible_role = Role.objects.create(name='invisible_role', ou=simple_user.ou)
1088 1122
    admin_of_simple_role = simple_role.get_admin_role()
1089 1123

  
1090 1124
    admin_of_simple_role.members.add(simple_user)
1091
    view_role_perm = get_permission_model().objects.create(
1092
        operation=get_operation(VIEW_OP),
1093
        target_ct=ContentType.objects.get_for_model(Role),
1094
        target_id=visible_role.pk,
1095
    )
1096
    simple_role.permissions.add(view_role_perm)
1125
    for role in (visible_role, visible_role_2):
1126
        view_role_perm = get_permission_model().objects.create(
1127
            operation=get_operation(VIEW_OP),
1128
            target_ct=ContentType.objects.get_for_model(Role),
1129
            target_id=role.pk,
1130
        )
1131
        simple_role.permissions.add(view_role_perm)
1097 1132
    simple_user.roles.add(simple_role)
1098 1133

  
1099 1134
    response = login(app, simple_user, '/manage/roles/')
1100 1135

  
1101
    # all visible roles are shown
1136
    # all visible roles are shown, except current role
1102 1137
    response = app.get('/manage/roles/%s/add-child/' % simple_role.pk)
1103
    assert {visible_role.pk, simple_role.pk} == get_choices(response)
1104

  
1105
    # all roles with manage_members permissions are shown
1106
    response = app.get('/manage/roles/%s/add-parent/' % simple_role.pk)
1107
    assert {simple_role.pk, admin_of_simple_role.pk} == get_choices(response)
1108

  
1109
    response = app.get('/manage/roles/%s/add-parent/' % visible_role.pk)
1110
    assert {simple_role.pk, admin_of_simple_role.pk} == get_choices(response)
1111

  
1112

  
1113
def test_manager_widgets_field_id_other_user(app, admin, simple_user, simple_role):
1114
    other_role = Role.objects.create(name='visible_role', ou=simple_user.ou)
1115
    simple_role.get_admin_role().members.add(simple_user)
1116

  
1117
    response = login(app, admin, '/manage/roles/%s/add-child/' % simple_role.pk)
1118
    select2_json = request_select2(app, response)
1119
    assert select2_json['more'] is False
1138
    q = response.pyquery.remove_namespaces()
1139
    assert len(q('table tbody tr')) == 2
1140
    assert {e.text_content() for e in q('table tbody td.name')} == {visible_role.name, visible_role_2.name}
1120 1141

  
1121
    # admin can see every roles
1122
    assert {simple_role.pk, other_role.pk} == {result['id'] for result in select2_json['results']}
1142
    # filter by ou
1143
    response.form['search-ou'] = ou1.pk
1144
    response = response.form.submit()
1145
    q = response.pyquery.remove_namespaces()
1146
    assert len(q('table tbody tr')) == 1
1147
    assert {e.text_content() for e in q('table tbody td.name')} == {visible_role_2.name}
1123 1148

  
1124
    login(app, simple_user)
1125
    # same request from the page served for admin
1126
    select2_json = request_select2(app, response)
1127
    # simple_user doesn't see all roles
1128
    assert simple_role.pk == select2_json['results'][0]['id']
1149
    # filter by name
1150
    response.form['search-text'] = '2'
1151
    response.form['search-ou'] = 'all'
1152
    response = response.form.submit()
1153
    q = response.pyquery.remove_namespaces()
1154
    assert len(q('table tbody tr')) == 1
1155
    assert {e.text_content() for e in q('table tbody td.name')} == {visible_role_2.name}
1129 1156

  
1130
    # anymous user receive 404
1131
    app.session.flush()
1132
    select2_json = request_select2(app, response, get_kwargs={'status': 404})
1157
    # all roles with manage_members permissions are shown
1158
    response = app.get('/manage/roles/%s/add-parent/' % visible_role.pk)
1159
    q = response.pyquery.remove_namespaces()
1160
    assert len(q('table tbody tr')) == 1
1161
    assert {e.text_content() for e in q('table tbody td.name')} == {simple_role.name}
1133 1162

  
1134 1163

  
1135 1164
def test_display_parent_roles_on_role_page(app, superuser, settings):
1136
-