From 92106e522d9f3e14a3c86ddd3eef63352f6e734a Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Mon, 18 Nov 2019 16:40:05 +0100 Subject: [PATCH 2/2] manager: prevent changing ldap-sychronised role affectations (#37187) --- src/authentic2/manager/tables.py | 4 ++- .../authentic2/manager/user_roles_table.html | 6 +++- src/authentic2/manager/user_views.py | 15 +++++++-- src/authentic2/manager/views.py | 17 +++++++++- tests/test_ldap.py | 32 +++++++++++++++++++ 5 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/authentic2/manager/tables.py b/src/authentic2/manager/tables.py index 224725f1..52d452a9 100644 --- a/src/authentic2/manager/tables.py +++ b/src/authentic2/manager/tables.py @@ -122,7 +122,9 @@ class OuUserRolesTable(tables.Table): 'indeterminate{%% endif %%}"' ' name="role-{{ record.pk }}" type="checkbox" {%% if record.member %%}checked{%% endif %%} ' '{%% if not record.has_perm %%}disabled ' - 'title="{%% trans "%s" %%}"{%% endif %%}/>' % ugettext_noop('You are not authorized to manage this role'), + 'title="{%% trans "%s" %%}"{%% endif %%} ' + '{%% if record.from_ldap %%}disabled ' + '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.')), verbose_name=_('Member'), order_by=('member', 'via', 'name')) diff --git a/src/authentic2/manager/templates/authentic2/manager/user_roles_table.html b/src/authentic2/manager/templates/authentic2/manager/user_roles_table.html index d173a54f..311308ab 100644 --- a/src/authentic2/manager/templates/authentic2/manager/user_roles_table.html +++ b/src/authentic2/manager/templates/authentic2/manager/user_roles_table.html @@ -7,6 +7,10 @@ {% endblock %} {% block table.tbody.last.column %} - {% if table.context.view.can_change and row.record.member %}{% endif %} + + {% if table.context.view.can_change and row.record.member %} + + {% endif %} + {% endblock %} {% endif %} diff --git a/src/authentic2/manager/user_views.py b/src/authentic2/manager/user_views.py index 3c1a8a15..6c350e02 100644 --- a/src/authentic2/manager/user_views.py +++ b/src/authentic2/manager/user_views.py @@ -19,6 +19,7 @@ import datetime import collections import operator +from django.conf import settings from django.db import models from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.html import format_html @@ -558,9 +559,19 @@ class UserRolesView(HideOUColumnMixin, BaseSubTableView): manageable_ids = map(str, qs2.values_list('pk', flat=True)) qs = qs.extra(select={'has_perm': 'a2_rbac_role.id in (%s)' % ', '.join(manageable_ids)}) qs = qs.exclude(slug__startswith='_a2-managers-of-role') - return qs else: - return self.object.roles_and_parents() + qs = self.object.roles_and_parents() + qs = self.flag_ldap_roles(qs) + return qs + + def flag_ldap_roles(self, qs): + mappings = getattr(settings, 'LDAP_AUTH_SETTINGS', [{}])[0].get('group_to_role_mapping', []) + if mappings: + role_names = [role for mapping in mappings for role in mapping[1]] + roles = qs.filter(name__in=role_names) + role_ids = map(str, roles.values_list('pk', flat=True)) + qs = qs.extra(select={'from_ldap': 'a2_rbac_role.id in (%s)' % ', '.join(role_ids)}) + return qs def get_table_data(self): qs = super(UserRolesView, self).get_table_data() diff --git a/src/authentic2/manager/views.py b/src/authentic2/manager/views.py index 825b5dee..4a74eae8 100644 --- a/src/authentic2/manager/views.py +++ b/src/authentic2/manager/views.py @@ -17,6 +17,8 @@ import json import inspect +from django.conf import settings +from django.contrib import messages from django.core.exceptions import PermissionDenied, ValidationError from django.db import transaction from django.views.generic.base import ContextMixin @@ -38,7 +40,7 @@ from django_select2.views import AutoResponseView from gadjo.templatetags.gadjo import xstatic -from django_rbac.utils import get_ou_model +from django_rbac.utils import get_ou_model, get_role_model from authentic2.data_transfer import export_site, import_site, ImportContext from authentic2.forms.profile import modelform_factory @@ -120,6 +122,8 @@ class PermissionMixin(object): perm = '%s.%s_%s' % (app_label, permission, model_name) setattr(self, 'can_' + permission, request.user.has_perm(perm, self.object)) + if self.can_manage_members: + self.check_for_ldap_sync() if self.permissions \ and not request.user.has_perms( self.permissions, self.object): @@ -134,6 +138,17 @@ class PermissionMixin(object): if not self.permissions_global and not request.user.has_perm_any(self.permissions): raise PermissionDenied + def check_for_ldap_sync(self): + if not isinstance(self.object, get_role_model()): + return + mappings = getattr(settings, 'LDAP_AUTH_SETTINGS', [{}])[0].get('group_to_role_mapping', []) + if mappings and any(role for mapping in mappings for role in mapping[1] + if role == self.object.name): + messages.info(self.request, + 'This role is synchronised from LDAP, changing members is not allowed.') + self.can_manage_members = False + + def dispatch(self, request, *args, **kwargs): response = self.authorize(request, *args, **kwargs) if response is not None: diff --git a/tests/test_ldap.py b/tests/test_ldap.py index bee2138f..d305a1c4 100644 --- a/tests/test_ldap.py +++ b/tests/test_ldap.py @@ -348,6 +348,38 @@ def test_posix_group_to_role_mapping(slapd, settings, client, db): assert response.context['user'].roles.count() == 1 +def test_group_to_role_mapping_modify_disabled(slapd, settings, db, app, admin, simple_user): + from authentic2.a2_rbac.models import Role + + role = Role.objects.get_or_create(name='Role2', ou=simple_user.ou)[0] + simple_user.roles.add(role) + settings.LDAP_AUTH_SETTINGS = [{ + 'url': [slapd.ldap_url], + 'basedn': u'o=ôrga', + 'use_tls': False, + 'group_to_role_mapping': [ + ['cn=group2,o=ôrga', ['Role2']], + ], + }] + utils.login(app, admin, '/manage/') + + response = app.get('/manage/users/%s/roles/?search-ou=all' % simple_user.pk) + q = response.pyquery.remove_namespaces() + assert q('table tbody td.name').text() == 'Role2' + assert q('table tbody td .disabled') + + response = app.get('/manage/users/%s/roles/?search-ou=%s' % (simple_user.pk, simple_user.ou.pk)) + q = response.pyquery.remove_namespaces() + assert q('table tbody td.name').text() == 'Role2' + assert q('table tbody td.member input').attr('disabled') + + response = app.get('/manage/roles/%s/' % (role.pk)) + q = response.pyquery.remove_namespaces() + assert not q('form.manager-m2m-add-form') + assert q('div.role-inheritance .role-add.disabled') + assert not q('table tbody td a.icon-remove-sign js-remove-object') + + def test_group_su(slapd, settings, client, db): from django.contrib.auth.models import Group -- 2.20.1