Projet

Général

Profil

0001-manager-forbid-changing-role-members-when-synced-fro.patch

Valentin Deniaud, 28 mai 2020 12:04

Télécharger (10,1 ko)

Voir les différences:

Subject: [PATCH] manager: forbid changing role members when synced from ldap
 (#37187)

 .../0023_role_manage_members_allowed.py       | 20 +++++++++
 src/authentic2/a2_rbac/models.py              |  4 ++
 src/authentic2/backends/ldap_backend.py       |  4 ++
 src/authentic2/manager/role_views.py          |  9 ++++
 src/authentic2/manager/tables.py              |  4 +-
 .../authentic2/manager/role_members.html      |  5 +++
 .../authentic2/manager/user_roles_table.html  |  6 ++-
 tests/test_ldap.py                            | 41 +++++++++++++++++--
 8 files changed, 87 insertions(+), 6 deletions(-)
 create mode 100644 src/authentic2/a2_rbac/migrations/0023_role_manage_members_allowed.py
src/authentic2/a2_rbac/migrations/0023_role_manage_members_allowed.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2020-05-27 13:22
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6

  
7

  
8
class Migration(migrations.Migration):
9

  
10
    dependencies = [
11
        ('a2_rbac', '0022_auto_20200402_1101'),
12
    ]
13

  
14
    operations = [
15
        migrations.AddField(
16
            model_name='role',
17
            name='manage_members_allowed',
18
            field=models.BooleanField(default=True, verbose_name='Allow adding or deleting role members'),
19
        ),
20
    ]
src/authentic2/a2_rbac/models.py
227 227
                                  content_type_field='target_ct',
228 228
                                  object_id_field='target_id')
229 229

  
230
    manage_members_allowed = models.BooleanField(
231
        default=True,
232
        verbose_name=_('Allow adding or deleting role members'))
233

  
230 234
    def get_admin_role(self, create=True):
231 235
        from . import utils
232 236

  
src/authentic2/backends/ldap_backend.py
783 783
                # Remove extra roles
784 784
                elif dn not in role_dns and role in roles:
785 785
                    user.roles.remove(role)
786
                if role.manage_members_allowed:
787
                    log.info('role %s is now only manageable through LDAP', role)
788
                    role.manage_members_allowed = False
789
                    role.save()
786 790

  
787 791
    def get_ldap_group_dns(self, user, dn, conn, block, attributes):
788 792
        '''Retrieve group DNs from the LDAP by attributes (memberOf) or by
src/authentic2/manager/role_views.py
149 149
    def title(self):
150 150
        return self.get_instance_name()
151 151

  
152
    @property
153
    def can_manage_members(self):
154
        return self.object.manage_members_allowed and getattr(self, '_can_manage_members', False)
155

  
156
    @can_manage_members.setter
157
    def can_manage_members(self, value):
158
        self._can_manage_members = value
159

  
152 160
    def get_table_queryset(self):
153 161
        return self.object.all_members()
154 162

  
......
189 197
        ctx['admin_roles'] = views.filter_view(self.request,
190 198
                                               self.object.get_admin_role().children(include_self=False,
191 199
                                                                                     annotate=True))
200
        ctx['from_ldap'] = self._can_manage_members and not self.can_manage_members
192 201
        return ctx
193 202

  
194 203
members = RoleMembersView.as_view()
src/authentic2/manager/tables.py
122 122
        'indeterminate{%% endif %%}"'
123 123
        ' name="role-{{ record.pk }}" type="checkbox" {%% if record.member %%}checked{%% endif %%} '
124 124
        '{%% if not record.has_perm %%}disabled '
125
        'title="{%% trans "%s" %%}"{%% endif %%}/>' % ugettext_noop('You are not authorized to manage this role'),
125
        'title="{%% trans "%s" %%}"{%% endif %%} '
126
        '{%% if not record.manage_members_allowed %%}disabled '
127
        'title="{%% trans "%s" %%}"{%% endif %%}/>' % (ugettext_noop('You are not authorized to manage this role'), ugettext_noop('This role is synchronised from LDAP, changing members is not allowed.')),
126 128
        verbose_name=_('Member'),
127 129
        order_by=('member', 'via', 'name'))
128 130

  
src/authentic2/manager/templates/authentic2/manager/role_members.html
46 46
{% endblock %}
47 47

  
48 48
{% block main %}
49
 {% if from_ldap %}
50
   <div class="infonotice">
51
     {% trans "This role is synchronised from LDAP, changing members is not allowed." %}
52
   </div>
53
 {% endif %}
49 54
 {% with row_link=1 url_name="a2-manager-user-detail" %}
50 55
   {% render_table table "authentic2/manager/role_members_table.html" %}
51 56
 {% endwith %}
src/authentic2/manager/templates/authentic2/manager/user_roles_table.html
7 7
    <th></th>
8 8
  {% endblock %}
9 9
  {% block table.tbody.last.column %}
10
  <td>{% if table.context.view.can_change and row.record.member %}<a class="icon-remove-sign js-remove-object" data-confirm="{% blocktrans with name=row.record.name username=table.context.object.get_full_name %}Do you really want to remove role &quot;{{ name }}&quot; from user &quot;{{ username }}&quot;&nbsp;?{% endblocktrans %}" href="#" data-pk-arg="role"></a>{% endif %}</td>
10
  <td>
11
    {% if table.context.view.can_change and row.record.member %}
12
      <a class="icon-remove-sign {% if not row.record.allow_manage_members %} disabled {% else %} js-remove-object {% endif %}" data-confirm="{% blocktrans with name=row.record.name username=table.context.object.get_full_name %}Do you really want to remove role &quot;{{ name }}&quot; from user &quot;{{ username }}&quot;&nbsp;?{% endblocktrans %}" href="#" data-pk-arg="role" title="{% if not row.record.allow_manage_members %}{% trans "This role is synchronised from LDAP, changing members is not allowed." %}{% endif %}"></a>
13
    {% endif %}
14
  </td>
11 15
  {% endblock %}
12 16
{% endif %}
tests/test_ldap.py
34 34
from django.utils.six.moves.urllib import parse as urlparse
35 35

  
36 36
from authentic2.models import Service
37
from authentic2.a2_rbac.models import Role
37 38
from authentic2.a2_rbac.utils import get_default_ou
38 39
from django_rbac.utils import get_ou_model
39 40
from authentic2.backends import ldap_backend
......
310 311

  
311 312

  
312 313
def test_group_to_role_mapping(slapd, settings, client, db):
313
    from authentic2.a2_rbac.models import Role
314

  
315 314
    Role.objects.get_or_create(name='Role1')
316 315
    settings.LDAP_AUTH_SETTINGS = [{
317 316
        'url': [slapd.ldap_url],
......
329 328

  
330 329

  
331 330
def test_posix_group_to_role_mapping(slapd, settings, client, db):
332
    from authentic2.a2_rbac.models import Role
333

  
334 331
    Role.objects.get_or_create(name='Role2')
335 332
    settings.LDAP_AUTH_SETTINGS = [{
336 333
        'url': [slapd.ldap_url],
......
348 345
    assert response.context['user'].roles.count() == 1
349 346

  
350 347

  
348
def test_group_to_role_mapping_modify_disabled(slapd, settings, db, app, admin, client):
349
    role = Role.objects.create(name='Role3')
350
    settings.LDAP_AUTH_SETTINGS = [{
351
        'url': [slapd.ldap_url],
352
        'basedn': u'o=ôrga',
353
        'use_tls': False,
354
        'group_to_role_mapping': [
355
            ['cn=group1,o=ôrga', ['Role3']],
356
        ],
357
    }]
358
    response = client.post('/login/', {'login-password-submit': '1',
359
                                       'username': USERNAME,
360
                                       'password': PASS}, follow=True)
361
    user = response.context['user']
362
    assert user.roles.count() == 1
363

  
364
    utils.login(app, admin, '/manage/')
365

  
366
    response = app.get('/manage/users/%s/roles/?search-ou=%s' % (user.pk, user.ou.pk))
367
    q = response.pyquery.remove_namespaces()
368
    assert q('table tbody td.name').text() == 'Role3'
369
    assert q('table tbody td.member input').attr('disabled')
370

  
371
    response = app.get('/manage/users/%s/roles/?search-ou=all' % user.pk)
372
    q = response.pyquery.remove_namespaces()
373
    assert q('table tbody td.name').text() == 'Role3'
374
    assert q('table tbody td.member input').attr('disabled')
375

  
376
    response = app.get('/manage/roles/%s/' % (role.pk))
377
    assert 'synchronised from LDAP' in response.text
378
    q = response.pyquery.remove_namespaces()
379
    assert not q('form.manager-m2m-add-form')
380
    assert q('div.role-inheritance .role-add.disabled')
381
    assert not q('table tbody td a.icon-remove-sign js-remove-object')
382

  
383

  
351 384
def test_group_su(slapd, settings, client, db):
352 385
    from django.contrib.auth.models import Group
353 386

  
354
-